From 0a80d5a0e8542f4b4c6054d7a7c58982a2a3b176 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 15:39:23 +0100 Subject: [PATCH 001/316] The dominator tree of a CFG can now be derived. --- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 15 +++ .../org/opalj/br/cfg/AbstractCFGTest.scala | 17 ++- .../org/opalj/br/cfg/DominatorTreeTest.scala | 117 ++++++++++++++++++ 3 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index cfb0b0d915..753030520c 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -722,6 +722,21 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( ) } + /** + * @return Returns the dominator tree of this CFG. + * + * @see [[DominatorTree.apply]] + */ + def dominatorTree: DominatorTree = { + DominatorTree( + 0, + basicBlocks.head.predecessors.nonEmpty, + foreachSuccessor, + foreachPredecessor, + basicBlocks.last.endPC + ) + } + // --------------------------------------------------------------------------------------------- // // Visualization & Debugging diff --git a/OPAL/br/src/test/scala/org/opalj/br/cfg/AbstractCFGTest.scala b/OPAL/br/src/test/scala/org/opalj/br/cfg/AbstractCFGTest.scala index 4abc62976c..8afb7b748b 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/cfg/AbstractCFGTest.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/cfg/AbstractCFGTest.scala @@ -5,9 +5,9 @@ package cfg import org.scalatest.FunSpec import org.scalatest.Matchers import org.scalatest.BeforeAndAfterAll - import org.opalj.io.writeAndOpen import org.opalj.br.instructions.Instruction +import org.opalj.graphs.DominatorTree /** * Helper methods to test the CFG related methods. @@ -87,11 +87,15 @@ abstract class AbstractCFGTest extends FunSpec with Matchers with BeforeAndAfter assert((code.cfJoins -- cfJoins).isEmpty) } - /** If the execution of `f` results in an exception the CFG is printed. */ + /** + * If the execution of `f` results in an exception the CFG is printed. + * In case the dominator tree is to be printed as well, provide a defined dominator tree. + */ def printCFGOnFailure( - method: Method, - code: Code, - cfg: CFG[Instruction, Code] + method: Method, + code: Code, + cfg: CFG[Instruction, Code], + domTree: Option[DominatorTree] = None )( f: ⇒ Unit )( @@ -104,6 +108,9 @@ abstract class AbstractCFGTest extends FunSpec with Matchers with BeforeAndAfter } catch { case t: Throwable ⇒ writeAndOpen(cfg.toDot, method.name+"-CFG", ".gv") + if (domTree.isDefined) { + writeAndOpen(domTree.get.toDot(), method.name+"-DomTree", ".gv") + } throw t } } diff --git a/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala b/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala new file mode 100644 index 0000000000..a6c61a341e --- /dev/null +++ b/OPAL/br/src/test/scala/org/opalj/br/cfg/DominatorTreeTest.scala @@ -0,0 +1,117 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.br.cfg + +import java.net.URL + +import org.junit.runner.RunWith +import org.opalj.br.analyses.Project +import org.opalj.br.ClassHierarchy +import org.opalj.br.ObjectType +import org.opalj.br.TestSupport.biProject +import org.opalj.br.instructions.IF_ICMPNE +import org.opalj.br.instructions.IFNE +import org.opalj.br.Code +import org.opalj.br.instructions.IFEQ +import org.opalj.br.instructions.ILOAD +import org.scalatest.junit.JUnitRunner + +/** + * Computes the dominator tree of CFGs of a couple of methods and checks their sanity. + * + * @author Patrick Mell + */ +@RunWith(classOf[JUnitRunner]) +class DominatorTreeTest extends AbstractCFGTest { + + /** + * Takes an `index` and finds the next not-null instruction within code after `index`. + * The index of that instruction is then returned. In case no instruction could be found, the + * value of `index` is returned. + */ + private def getNextNonNullInstr(index: Int, code: Code): Int = { + var foundIndex = index + var found = false + for (i ← (index + 1).to(code.instructions.length)) { + if (!found && code.instructions(i) != null) { + foundIndex = i + found = true + } + } + foundIndex + } + + describe("Sanity of dominator trees of control flow graphs") { + + val testProject: Project[URL] = biProject("controlflow.jar") + val boringTestClassFile = testProject.classFile(ObjectType("controlflow/BoringCode")).get + + implicit val testClassHierarchy: ClassHierarchy = testProject.classHierarchy + + it("the dominator tree of a CFG with no control flow should be a tree where each "+ + "instruction is strictly dominator by its previous instruction (except for the root)") { + val m = boringTestClassFile.findMethod("singleBlock").head + val code = m.body.get + val cfg = CFGFactory(code) + val domTree = cfg.dominatorTree + + printCFGOnFailure(m, code, cfg, Some(domTree)) { + domTree.immediateDominators.zipWithIndex.foreach { + case (idom, index) ⇒ + if (index == 0) { + idom should be(0) + } else { + idom should be(index - 1) + } + } + } + } + + it("in a dominator tree of a CFG with control instructions, the first instruction within "+ + "that control structure should be dominated by the controlling instruction (like "+ + "an if)") { + val m = boringTestClassFile.findMethod("conditionalTwoReturns").head + val code = m.body.get + val cfg = CFGFactory(code) + val domTree = cfg.dominatorTree + + printCFGOnFailure(m, code, cfg, Some(domTree)) { + var index = 0 + code.foreachInstruction { next ⇒ + next match { + case _: IFNE | _: IF_ICMPNE ⇒ + val next = getNextNonNullInstr(index, code) + domTree.immediateDominators(next) should be(index) + case _ ⇒ + } + index += 1 + } + } + } + + it("in a dominator tree of a CFG with an if-else right before the return, the return "+ + "should be dominated by the if check of the if-else") { + val m = boringTestClassFile.findMethod("conditionalOneReturn").head + val code = m.body.get + val cfg = CFGFactory(code) + val domTree = cfg.dominatorTree + + printCFGOnFailure(m, code, cfg, Some(domTree)) { + val loadOfReturnOption = code.instructions.reverse.find(_.isInstanceOf[ILOAD]) + loadOfReturnOption should not be loadOfReturnOption.isEmpty + + val loadOfReturn = loadOfReturnOption.get + val indexOfLoadOfReturn = code.instructions.indexOf(loadOfReturn) + val ifOfLoadOfReturn = code.instructions.reverse.zipWithIndex.find { + case (instr, i) ⇒ + i < indexOfLoadOfReturn && instr.isInstanceOf[IFEQ] + } + ifOfLoadOfReturn should not be ifOfLoadOfReturn.isEmpty + + val indexOfIf = code.instructions.indexOf(ifOfLoadOfReturn.get._1) + domTree.immediateDominators(indexOfLoadOfReturn) should be(indexOfIf) + } + } + + } + +} From aa2b7a34981fac1cc1d3e0461f601bfd8183a51e Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:08:03 +0100 Subject: [PATCH 002/316] Implemented an analysis that determines the string constancy level as well as the possible values for a read operation of a local string variable. --- .../string_definition/TestMethods.java | 101 +++++++++ .../StringConstancyLevel.java | 27 +++ .../string_definition/StringDefinitions.java | 45 ++++ .../fpcf/LocalStringDefinitionTest.scala | 130 +++++++++++ .../LocalStringDefinitionMatcher.scala | 32 +++ .../LocalStringDefinitionAnalysis.scala | 201 ++++++++++++++++++ .../analyses/string_definition/package.scala | 26 +++ .../properties/StringConstancyProperty.scala | 79 +++++++ 8 files changed, 641 insertions(+) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java create mode 100644 DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala create mode 100644 DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java new file mode 100644 index 0000000000..d240f8aad4 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -0,0 +1,101 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_definition; + +import org.opalj.fpcf.properties.string_definition.StringConstancyLevel; +import org.opalj.fpcf.properties.string_definition.StringDefinitions; + +/** + * @author Patrick Mell + */ +public class TestMethods { + + @StringDefinitions( + value = "read-only string, trivial case", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedValues = { "java.lang.String" }, + pc = 4 + ) + public void constantString() { + String className = "java.lang.String"; + try { + Class.forName(className); + } catch (ClassNotFoundException ignored) { + } + } + + @StringDefinitions( + value = "checks if the string value for the *forName* call is correctly determined", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedValues = { "java.lang.string" }, + pc = 31 + ) + public void stringConcatenation() { + String className = "java.lang."; + System.out.println(className); + className += "string"; + try { + Class.forName(className); + } catch (ClassNotFoundException ignored) { + } + } + + @StringDefinitions( + value = "at this point, function call cannot be handled => DYNAMIC", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedValues = { "*" }, + pc = 6 + ) + public void fromFunctionCall() { + String className = getStringBuilderClassName(); + try { + Class.forName(className); + } catch (ClassNotFoundException ignored) { + } + } + + @StringDefinitions( + value = "constant string + string from function call => PARTIALLY_CONSTANT", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedValues = { "java.lang.*" }, + pc = 33 + ) + public void fromConstantAndFunctionCall() { + String className = "java.lang."; + System.out.println(className); + className += getSimpleStringBuilderClassName(); + try { + Class.forName(className); + } catch (ClassNotFoundException ignored) { + } + } + + @StringDefinitions( + value = "array access with unknown index", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedValues = { + "java.lang.String", "java.lang.StringBuilder", + "java.lang.System", "java.lang.Runnable" + }, pc = 38 + ) + public void fromStringArray(int index) { + String[] classes = { + "java.lang.String", "java.lang.StringBuilder", + "java.lang.System", "java.lang.Runnable" + }; + if (index >= 0 && index < classes.length) { + try { + Class.forName(classes[index]); + } catch (ClassNotFoundException ignored) { + } + } + } + + private String getStringBuilderClassName() { + return "java.lang.StringBuilder"; + } + + private String getSimpleStringBuilderClassName() { + return "StringBuilder"; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java new file mode 100644 index 0000000000..3fc62ea493 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_definition; + +/** + * Java annotations do not work with Scala enums, such as + * {@link org.opalj.fpcf.properties.StringConstancyLevel}. Thus, this enum. + * + * @author Patrick Mell + */ +public enum StringConstancyLevel { + + // For details, see {@link org.opalj.fpcf.properties.StringConstancyLevel}. + CONSTANT("constant"), + PARTIALLY_CONSTANT("partially-constant"), + DYNAMIC("dynamic"); + + private final String value; + + StringConstancyLevel(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java new file mode 100644 index 0000000000..9e2a6081aa --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_definition; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +/** + * The StringDefinitions annotation states how a string field or local variable is used during a + * program execution. + * + * @author Patrick Mell + */ +@PropertyValidator(key = "StringDefinitions", validator = LocalStringDefinitionMatcher.class) +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface StringDefinitions { + + /** + * A short reasoning of this property. + */ + String value() default "N/A"; + + /** + * This value determines the expectedLevel of freedom for a string field or local variable to be + * changed. The default value is {@link StringConstancyLevel#DYNAMIC}. + */ + StringConstancyLevel expectedLevel() default StringConstancyLevel.DYNAMIC; + + /** + * A set of string elements that are expected. If exact matching is desired, insert only one + * element. Otherwise, a super set may be specified, e.g., if some value from an array is + * expected. + */ + String[] expectedValues() default ""; + + /** + * `pc` identifies the program counter of the statement for which a `UVar` is to be + * extracted for a test. Note that if an expression has more than one `UVar`, the test suite + * is free to choose which one it actually uses for its test(s). + */ + int pc(); + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala new file mode 100644 index 0000000000..f0766c6ce7 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -0,0 +1,130 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf + +import java.io.File + +import org.opalj.br.analyses.Project +import org.opalj.br.Annotation +import org.opalj.collection.immutable.RefArray +import org.opalj.fpcf.analyses.cg.V +import org.opalj.fpcf.analyses.string_definition.LazyStringTrackingAnalysis +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.DefaultTACAIKey +import org.opalj.tac.DUVar +import org.opalj.tac.ExprStmt +import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.Stmt +import org.opalj.value.KnownTypedValue + +/** + * Tests whether the StringTrackingAnalysis works correctly. + * + * @author Patrick Mell + */ +class LocalStringDefinitionTest extends PropertiesTest { + + val stringUsageAnnotationName = "org.opalj.fpcf.properties.string_tracking.StringDefinitions" + + /** + * Extracts a [[org.opalj.tac.UVar]] from a set of statements. The location of the UVar is + * identified by a program counter. + * + * @note If the desired statement contains more than one UVar, only the very first is returned. + * + * @param stmts The statements from which to extract the UVar. The statement is to be expected + * to be an [[ExprStmt]]. + * @return Returns the element from the statement that is identified by `pc`. In case the + * statement identified by pc is not present or the statement does not contain a UVar, + * `None` is returned. + */ + private def extractUVar(stmts: Array[Stmt[V]], pc: UShort): Option[V] = { + val stmt = stmts.filter(_.pc == pc) + if (stmt.isEmpty) { + return None + } + + // TODO: What is a more generic / better way than nesting so deep? + stmt.head match { + case ExprStmt(_, expr) ⇒ + expr match { + case StaticFunctionCall(_, _, _, _, _, params) ⇒ + val vars = params.filter(_.isInstanceOf[DUVar[KnownTypedValue]]) + if (vars.isEmpty) { + None + } + Option(vars.head.asVar) + case _ ⇒ None + } + case _ ⇒ None + } + } + + /** + * Takes an annotation and checks if it is a + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]] annotation. + * + * @param a The annotation to check. + * @return True if the `a` is of type StringDefinitions and false otherwise. + */ + private def isStringUsageAnnotation(a: Annotation): Boolean = + // TODO: Is there a better way than string comparison? + a.annotationType.toJavaClass.getName == stringUsageAnnotationName + + /** + * Extracts the program counter from a + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]] if present. + * + * @param annotations A set of annotations which is to be scanned. The only annotation that is + * processed is + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * @return Returns the `pc` value from the StringDefinitions if present. Otherwise `None` is + * returned. + */ + private def pcFromAnnotations(annotations: RefArray[Annotation]): Option[UShort] = { + val annotation = annotations.filter(isStringUsageAnnotation) + if (annotation.isEmpty) { + None + } + + Option(annotation.head.elementValuePairs.filter { + _.name == "pc" + }.head.value.asIntValue.value) + } + + describe("the org.opalj.fpcf.StringTrackingAnalysis is executed") { + val as = executeAnalyses(Set(LazyStringTrackingAnalysis)) + as.propertyStore.shutdown() + validateProperties(as, fieldsWithAnnotations(as.project), Set("StringDefinitions")) + } + + describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { + val cutPath = System.getProperty("user.dir")+ + "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/fixtures/string_tracking/TestMethods.class" + val p = Project(new File(cutPath)) + val ps = p.get(org.opalj.fpcf.PropertyStoreKey) + ps.setupPhase(Set(StringConstancyProperty)) + + LazyStringTrackingAnalysis.init(p, ps) + LazyStringTrackingAnalysis.schedule(ps, null) + val tacProvider = p.get(DefaultTACAIKey) + + // Call the analysis for all methods annotated with @StringDefinitions + p.allMethodsWithBody.filter { + _.runtimeInvisibleAnnotations.foldLeft(false)( + (exists, a) ⇒ exists || isStringUsageAnnotation(a) + ) + }.foreach { m ⇒ + pcFromAnnotations(m.runtimeInvisibleAnnotations) match { + case Some(counter) ⇒ extractUVar(tacProvider(m).stmts, counter) match { + case Some(uvar) ⇒ + ps.force(Tuple2(uvar, m), StringConstancyProperty.key) + ps.waitOnPhaseCompletion() + case _ ⇒ + } + case _ ⇒ + } + } + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala new file mode 100644 index 0000000000..952d38d36a --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -0,0 +1,32 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_definition + +import org.opalj.br.analyses.Project +import org.opalj.br.AnnotationLike +import org.opalj.br.ObjectType +import org.opalj.fpcf.properties.AbstractPropertyMatcher +import org.opalj.fpcf.Property + +/** + * Matches local variable's `StringConstancy` property. The match is successful if the + * variable has a constancy level that matches its usage and the expected values are present. + * + * @author Patrick Mell + */ +class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { + + /** + * @inheritdoc + */ + override def validateProperty( + p: Project[_], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Traversable[Property] + ): Option[String] = { + None + // TODO: Implement the matcher + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala new file mode 100644 index 0000000000..f7a1e4394c --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -0,0 +1,201 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.FPCFAnalysis +import org.opalj.fpcf.FPCFEagerAnalysisScheduler +import org.opalj.fpcf.PropertyComputationResult +import org.opalj.fpcf.PropertyKind +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result +import org.opalj.fpcf.properties.StringConstancyLevel._ +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.ArrayLoad +import org.opalj.tac.ArrayStore +import org.opalj.tac.Assignment +import org.opalj.tac.Expr +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.Stmt +import org.opalj.tac.StringConst +import org.opalj.tac.VirtualFunctionCall + +import scala.collection.mutable.ArrayBuffer + +class StringTrackingAnalysisContext( + val stmts: Array[Stmt[V]] +) + +/** + * LocalStringDefinitionAnalysis processes a read operation of a local string variable at a program + * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. + * + * "Local" as this analysis takes into account only the enclosing function as a context. Values + * coming from other functions are regarded as dynamic values even if the function returns a + * constant string value. [[StringConstancyProperty]] models this by inserting "*" into the set of + * possible strings. + * + * StringConstancyProperty might contain more than one possible string, e.g., if the source of the + * value is an array. + * + * @author Patrick Mell + */ +class LocalStringDefinitionAnalysis private[analyses] ( + val project: SomeProject +) extends FPCFAnalysis { + + def analyze( + data: P + ): PropertyComputationResult = { + // TODO: What is a better way to test if the given DUVar is of a certain type? + val simpleClassName = data._1.value.getClass.getSimpleName + simpleClassName match { + case "StringValue" ⇒ processStringValue(data) + case "SObjectValue" ⇒ processSObjectValue(data) + case _ ⇒ throw new IllegalArgumentException( + s"cannot process given UVar type ($simpleClassName)" + ) + } + } + + /** + * Processes the case that the UVar is a string value. + */ + private def processStringValue(data: P): PropertyComputationResult = { + val tacProvider = p.get(SimpleTACAIKey) + val methodStmts = tacProvider(data._2).stmts + + val assignedValues = ArrayBuffer[String]() + val level = CONSTANT + + val defSites = data._1.definedBy + defSites.filter(_ >= 0).foreach(defSite ⇒ { + val Assignment(_, _, expr) = methodStmts(defSite) + expr match { + case s: StringConst ⇒ + assignedValues += s.value + // TODO: Non-constant strings are not taken into consideration; problem? + case _ ⇒ + } + }) + + Result(data, StringConstancyProperty(level, assignedValues)) + } + + /** + * Processes the case that the UVar is of type `SObjectValue`. + */ + private def processSObjectValue(data: P): PropertyComputationResult = { + val tacProvider = p.get(SimpleTACAIKey) + val stmts = tacProvider(data._2).stmts + + val defSite = data._1.definedBy.filter(_ >= 0) + // TODO: Consider case for more than one defSite? Example for that? + val expr = stmts(defSite.head).asAssignment.expr + expr match { + case _: NonVirtualFunctionCall[V] ⇒ + // Local analysis => no processing + Result(data, StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))) + + case VirtualFunctionCall(_, _, _, _, _, receiver, _) ⇒ + val intermResult = processVirtualFuncCall(stmts, receiver) + Result(data, StringConstancyProperty(intermResult._1, intermResult._2)) + + case ArrayLoad(_, _, arrRef) ⇒ + // For assignments which use arrays, determine all possible values + val arrDecl = stmts(arrRef.asVar.definedBy.head) + val arrValues = arrDecl.asAssignment.targetVar.usedBy.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } map { f: Int ⇒ + val defSite = stmts(f).asArrayStore.value.asVar.definedBy.head + stmts(defSite).asAssignment.expr.asStringConst.value + } + Result(data, StringConstancyProperty(CONSTANT, arrValues.to[ArrayBuffer])) + } + } + + /** + * Processes the case that a function call is involved, e.g., to StringBuilder#append. + * + * @param stmts The surrounding context. For this analysis, the surrounding method. + * @param receiver Receiving object of the VirtualFunctionCall. + * @return Returns a tuple with the constancy level and the string value after the function + * call. + */ + private def processVirtualFuncCall( + stmts: Array[Stmt[V]], receiver: Expr[V] + ): (StringConstancyLevel, ArrayBuffer[String]) = { + var level = CONSTANT + + // TODO: Are these long concatenations the best / most robust way? + val appendCall = + stmts(receiver.asVar.definedBy.head).asAssignment.expr.asVirtualFunctionCall + + // Get previous value of string builder + val baseAssignment = stmts(appendCall.receiver.asVar.definedBy.head).asAssignment + val baseStr = valueOfAppendCall(baseAssignment.expr.asVirtualFunctionCall, stmts) + var assignedStr = baseStr._1 + // Get appended value and build the new string value + val appendData = valueOfAppendCall(appendCall, stmts) + if (appendData._2 == CONSTANT) { + assignedStr += appendData._1 + } else { + assignedStr += "*" + level = PARTIALLY_CONSTANT + } + Tuple2(level, ArrayBuffer(assignedStr)) + } + + /** + * Determines the string value that was passed to a `StringBuilder#append` method. This function + * can process string constants as well as function calls as argument to append. + * + * @param call A function call of `StringBuilder#append`. Note that for all other methods an + * [[IllegalArgumentException]] will be thrown. + * @param stmts The surrounding context. For this analysis, the surrounding method. + * @return For constants strings as arguments, this function returns the string value and the + * level [[CONSTANT]]. For function calls "*" (to indicate ''any + * value'') and [[DYNAMIC]]. + */ + private def valueOfAppendCall( + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + ): (String, Value) = { + // TODO: Check the base object as well + if (call.name != "append") { + throw new IllegalArgumentException("can only process StringBuilder#append calls") + } + + val defAssignment = call.params.head.asVar.definedBy.head + val assignExpr = stmts(defAssignment).asAssignment.expr + assignExpr match { + case _: NonVirtualFunctionCall[V] ⇒ Tuple2("*", DYNAMIC) + case StringConst(_, value) ⇒ (value, CONSTANT) + } + } + +} + +/** + * Executor for the lazy analysis. + */ +object LazyStringTrackingAnalysis extends FPCFEagerAnalysisScheduler { + + final override def uses: Set[PropertyKind] = Set.empty + + final override def derives: Set[PropertyKind] = Set(StringConstancyProperty) + + final override type InitializationData = Null + + final def init(p: SomeProject, ps: PropertyStore): Null = null + + def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} + + def afterPhaseCompletion(p: SomeProject, ps: PropertyStore): Unit = {} + + final override def start(p: SomeProject, ps: PropertyStore, unused: Null): FPCFAnalysis = { + val analysis = new LocalStringDefinitionAnalysis(p) + ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) + analysis + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala new file mode 100644 index 0000000000..9c503d9b51 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala @@ -0,0 +1,26 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses + +import org.opalj.br.Method +import org.opalj.tac.DUVar +import org.opalj.value.ValueInformation + +/** + * @author Patrick Mell + */ +package object string_definition { + + /** + * The type of entities the [[LocalStringDefinitionAnalysis]] processes. + * + * @note The analysis requires further context information, see [[P]]. + */ + type V = DUVar[ValueInformation] + + /** + * [[LocalStringDefinitionAnalysis]] processes a local variable within the context of a + * particular context, i.e., the method in which it is declared and used. + */ + type P = (V, Method) + +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala new file mode 100644 index 0000000000..d17e1404b4 --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -0,0 +1,79 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties + +import org.opalj.br.Field +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EPS +import org.opalj.fpcf.FallbackReason +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyKey +import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC + +import scala.collection.mutable.ArrayBuffer + +sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformation { + type Self = StringConstancyProperty +} + +/** + * Values in this enumeration represent the granularity of used strings. + * + * @author Patrick Mell + */ +object StringConstancyLevel extends Enumeration { + + type StringConstancyLevel = StringConstancyLevel.Value + + /** + * This level indicates that a string has a constant value at a given read operation. + */ + final val CONSTANT = Value("constant") + + /** + * This level indicates that a string is partially constant (constant + dynamic part) at some + * read operation, that is, the initial value of a string variable needs to be preserved. For + * instance, it is fine if a string variable is modified after its initialization by + * appending another string, s2. Later, s2 might be removed partially or entirely without + * violating the constraints of this level. + */ + final val PARTIALLY_CONSTANT = Value("partially-constant") + + /** + * This level indicates that a string at some read operations has an unpredictable value. + */ + final val DYNAMIC = Value("dynamic") + +} + +class StringConstancyProperty( + val constancyLevel: StringConstancyLevel.Value, + val properties: ArrayBuffer[String] +) extends Property with StringConstancyPropertyMetaInformation { + + final def key = StringConstancyProperty.key + +} + +object StringConstancyProperty extends StringConstancyPropertyMetaInformation { + + final val PropertyKeyName = "StringConstancy" + + final val key: PropertyKey[StringConstancyProperty] = { + PropertyKey.create( + PropertyKeyName, + (_: PropertyStore, _: FallbackReason, e: Entity) ⇒ { + // TODO: Using simple heuristics, return a better value for some easy cases + StringConstancyProperty(DYNAMIC, ArrayBuffer()) + }, + (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, + (_: PropertyStore, _: Entity) ⇒ None + ) + } + + def apply( + constancyLevel: StringConstancyLevel.Value, properties: ArrayBuffer[String] + ): StringConstancyProperty = new StringConstancyProperty(constancyLevel, properties) + +} From e2faa2f8804897387e28e9ce64785c0e1cc3e87d Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:10:50 +0100 Subject: [PATCH 003/316] The test interface for the StringDefinitionAnalysis was changed in a way that it no longer uses a program counter but a well-specified method call to identify a variable to analyze. --- .../string_definition/TestMethods.java | 51 +++++------ .../string_definition/StringDefinitions.java | 13 ++- .../fpcf/LocalStringDefinitionTest.scala | 91 ++++++------------- 3 files changed, 55 insertions(+), 100 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index d240f8aad4..618d00c7cb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -9,64 +9,60 @@ */ public class TestMethods { + /** + * This method represents the test method which is serves as the trigger point for the + * {@link org.opalj.fpcf.LocalStringDefinitionTest} to know which string read operation to + * analyze. + * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture + * only one read operation. For how to get around this limitation, see the annotation. + * + * @param s Some string which is to be analyzed. + */ + public void analyzeString(String s) { + } + @StringDefinitions( value = "read-only string, trivial case", expectedLevel = StringConstancyLevel.CONSTANT, - expectedValues = { "java.lang.String" }, - pc = 4 + expectedValues = { "java.lang.String" } ) public void constantString() { String className = "java.lang.String"; - try { - Class.forName(className); - } catch (ClassNotFoundException ignored) { - } + analyzeString(className); } @StringDefinitions( value = "checks if the string value for the *forName* call is correctly determined", expectedLevel = StringConstancyLevel.CONSTANT, - expectedValues = { "java.lang.string" }, - pc = 31 + expectedValues = { "java.lang.string" } ) public void stringConcatenation() { String className = "java.lang."; System.out.println(className); className += "string"; - try { - Class.forName(className); - } catch (ClassNotFoundException ignored) { - } + analyzeString(className); } @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedValues = { "*" }, - pc = 6 + expectedValues = { "*" } ) public void fromFunctionCall() { String className = getStringBuilderClassName(); - try { - Class.forName(className); - } catch (ClassNotFoundException ignored) { - } + analyzeString(className); } @StringDefinitions( value = "constant string + string from function call => PARTIALLY_CONSTANT", expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedValues = { "java.lang.*" }, - pc = 33 + expectedValues = { "java.lang.*" } ) public void fromConstantAndFunctionCall() { String className = "java.lang."; System.out.println(className); className += getSimpleStringBuilderClassName(); - try { - Class.forName(className); - } catch (ClassNotFoundException ignored) { - } + analyzeString(className); } @StringDefinitions( @@ -75,7 +71,7 @@ public void fromConstantAndFunctionCall() { expectedValues = { "java.lang.String", "java.lang.StringBuilder", "java.lang.System", "java.lang.Runnable" - }, pc = 38 + } ) public void fromStringArray(int index) { String[] classes = { @@ -83,10 +79,7 @@ public void fromStringArray(int index) { "java.lang.System", "java.lang.Runnable" }; if (index >= 0 && index < classes.length) { - try { - Class.forName(classes[index]); - } catch (ClassNotFoundException ignored) { - } + analyzeString(classes[index]); } } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index 9e2a6081aa..ce4cfe16b4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -8,6 +8,12 @@ /** * The StringDefinitions annotation states how a string field or local variable is used during a * program execution. + *

+ * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture + * only one read operation per test method. If this is a limitation, either (1) duplicate the + * corresponding test method and remove the first calls which trigger the analysis or (2) put the + * relevant code of the test function into a dedicated function and then call it from different + * test methods (to avoid copy&paste). * * @author Patrick Mell */ @@ -35,11 +41,4 @@ */ String[] expectedValues() default ""; - /** - * `pc` identifies the program counter of the statement for which a `UVar` is to be - * extracted for a test. Note that if an expression has more than one `UVar`, the test suite - * is free to choose which one it actually uses for its test(s). - */ - int pc(); - } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala index f0766c6ce7..9eb82ed140 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -6,16 +6,12 @@ import java.io.File import org.opalj.br.analyses.Project import org.opalj.br.Annotation -import org.opalj.collection.immutable.RefArray import org.opalj.fpcf.analyses.cg.V import org.opalj.fpcf.analyses.string_definition.LazyStringTrackingAnalysis import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.DefaultTACAIKey -import org.opalj.tac.DUVar -import org.opalj.tac.ExprStmt -import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt -import org.opalj.value.KnownTypedValue +import org.opalj.tac.VirtualMethodCall /** * Tests whether the StringTrackingAnalysis works correctly. @@ -24,40 +20,32 @@ import org.opalj.value.KnownTypedValue */ class LocalStringDefinitionTest extends PropertiesTest { - val stringUsageAnnotationName = "org.opalj.fpcf.properties.string_tracking.StringDefinitions" + val fqStringDefAnnotation = "org.opalj.fpcf.properties.string_definition.StringDefinitions" + val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_definition.TestMethods" + // The name of the method from which to extract DUVars to analyze + val nameTestMethod = "analyzeString" /** * Extracts a [[org.opalj.tac.UVar]] from a set of statements. The location of the UVar is - * identified by a program counter. + * identified the argument to the very first call to TestMethods#analyzeString. * - * @note If the desired statement contains more than one UVar, only the very first is returned. - * - * @param stmts The statements from which to extract the UVar. The statement is to be expected - * to be an [[ExprStmt]]. - * @return Returns the element from the statement that is identified by `pc`. In case the - * statement identified by pc is not present or the statement does not contain a UVar, - * `None` is returned. + * @param stmts The statements from which to extract the UVar, usually the method that contains + * the call to TestMethods#analyzeString. + * @return Returns the argument of the TestMethods#analyzeString as a DUVar. In case the + * expected analyze method is not present, None is returned. */ - private def extractUVar(stmts: Array[Stmt[V]], pc: UShort): Option[V] = { - val stmt = stmts.filter(_.pc == pc) - if (stmt.isEmpty) { - return None + private def extractUVar(stmts: Array[Stmt[V]]): Option[V] = { + val relMethodCalls = stmts.filter { + case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ + declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod + case _ ⇒ false } - // TODO: What is a more generic / better way than nesting so deep? - stmt.head match { - case ExprStmt(_, expr) ⇒ - expr match { - case StaticFunctionCall(_, _, _, _, _, params) ⇒ - val vars = params.filter(_.isInstanceOf[DUVar[KnownTypedValue]]) - if (vars.isEmpty) { - None - } - Option(vars.head.asVar) - case _ ⇒ None - } - case _ ⇒ None + if (relMethodCalls.isEmpty) { + return None } + + Some(relMethodCalls.head.asVirtualMethodCall.params.head.asVar) } /** @@ -68,29 +56,8 @@ class LocalStringDefinitionTest extends PropertiesTest { * @return True if the `a` is of type StringDefinitions and false otherwise. */ private def isStringUsageAnnotation(a: Annotation): Boolean = - // TODO: Is there a better way than string comparison? - a.annotationType.toJavaClass.getName == stringUsageAnnotationName - - /** - * Extracts the program counter from a - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]] if present. - * - * @param annotations A set of annotations which is to be scanned. The only annotation that is - * processed is - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. - * @return Returns the `pc` value from the StringDefinitions if present. Otherwise `None` is - * returned. - */ - private def pcFromAnnotations(annotations: RefArray[Annotation]): Option[UShort] = { - val annotation = annotations.filter(isStringUsageAnnotation) - if (annotation.isEmpty) { - None - } - - Option(annotation.head.elementValuePairs.filter { - _.name == "pc" - }.head.value.asIntValue.value) - } + // TODO: Is there a better way than string comparison? + a.annotationType.toJavaClass.getName == fqStringDefAnnotation describe("the org.opalj.fpcf.StringTrackingAnalysis is executed") { val as = executeAnalyses(Set(LazyStringTrackingAnalysis)) @@ -100,7 +67,7 @@ class LocalStringDefinitionTest extends PropertiesTest { describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { val cutPath = System.getProperty("user.dir")+ - "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/fixtures/string_tracking/TestMethods.class" + "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/fixtures/string_definition/TestMethods.class" val p = Project(new File(cutPath)) val ps = p.get(org.opalj.fpcf.PropertyStoreKey) ps.setupPhase(Set(StringConstancyProperty)) @@ -109,19 +76,15 @@ class LocalStringDefinitionTest extends PropertiesTest { LazyStringTrackingAnalysis.schedule(ps, null) val tacProvider = p.get(DefaultTACAIKey) - // Call the analysis for all methods annotated with @StringDefinitions p.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || isStringUsageAnnotation(a) ) - }.foreach { m ⇒ - pcFromAnnotations(m.runtimeInvisibleAnnotations) match { - case Some(counter) ⇒ extractUVar(tacProvider(m).stmts, counter) match { - case Some(uvar) ⇒ - ps.force(Tuple2(uvar, m), StringConstancyProperty.key) - ps.waitOnPhaseCompletion() - case _ ⇒ - } + } foreach { m ⇒ + extractUVar(tacProvider(m).stmts) match { + case Some(uvar) ⇒ + ps.force(Tuple2(uvar, m), StringConstancyProperty.key) + ps.waitOnPhaseCompletion() case _ ⇒ } } From 98221a6ffd90dea1aaae2fe44c3acb689d08bf14 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:40:16 +0100 Subject: [PATCH 004/316] Implemented the matcher for the LocalStringDefinitionAnalysis. --- .../StringConstancyLevel.java | 2 +- .../string_definition/StringDefinitions.java | 2 +- .../fpcf/LocalStringDefinitionTest.scala | 70 +++++++++++----- .../LocalStringDefinitionMatcher.scala | 82 +++++++++++++++++-- .../LocalStringDefinitionAnalysis.scala | 48 +++++++---- .../properties/StringConstancyProperty.scala | 18 ++-- 6 files changed, 170 insertions(+), 52 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java index 3fc62ea493..3e168a3d47 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java @@ -11,7 +11,7 @@ public enum StringConstancyLevel { // For details, see {@link org.opalj.fpcf.properties.StringConstancyLevel}. CONSTANT("constant"), - PARTIALLY_CONSTANT("partially-constant"), + PARTIALLY_CONSTANT("partially_constant"), DYNAMIC("dynamic"); private final String value; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index ce4cfe16b4..d87bcb7dba 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -17,7 +17,7 @@ * * @author Patrick Mell */ -@PropertyValidator(key = "StringDefinitions", validator = LocalStringDefinitionMatcher.class) +@PropertyValidator(key = "StringConstancy", validator = LocalStringDefinitionMatcher.class) @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD }) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala index 9eb82ed140..ee083d7361 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -6,13 +6,17 @@ import java.io.File import org.opalj.br.analyses.Project import org.opalj.br.Annotation +import org.opalj.br.Method import org.opalj.fpcf.analyses.cg.V -import org.opalj.fpcf.analyses.string_definition.LazyStringTrackingAnalysis +import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis +import org.opalj.fpcf.analyses.string_definition.LocalStringDefinitionAnalysis import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.VirtualMethodCall +import scala.collection.mutable + /** * Tests whether the StringTrackingAnalysis works correctly. * @@ -20,10 +24,19 @@ import org.opalj.tac.VirtualMethodCall */ class LocalStringDefinitionTest extends PropertiesTest { - val fqStringDefAnnotation = "org.opalj.fpcf.properties.string_definition.StringDefinitions" - val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_definition.TestMethods" - // The name of the method from which to extract DUVars to analyze - val nameTestMethod = "analyzeString" + /** + * @return Returns all relevant project files (NOT including library files) to run the tests. + */ + private def getRelevantProjectFiles: Array[File] = { + val necessaryFiles = Array( + "fixtures/string_definition/TestMethods.class", + "properties/string_definition/StringDefinitions.class" + ) + val basePath = System.getProperty("user.dir")+ + "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/" + + necessaryFiles.map { filePath ⇒ new File(basePath + filePath) } + } /** * Extracts a [[org.opalj.tac.UVar]] from a set of statements. The location of the UVar is @@ -37,7 +50,8 @@ class LocalStringDefinitionTest extends PropertiesTest { private def extractUVar(stmts: Array[Stmt[V]]): Option[V] = { val relMethodCalls = stmts.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ - declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod + declClass.toJavaClass.getName == LocalStringDefinitionTest.fqTestMethodsClass && + name == LocalStringDefinitionTest.nameTestMethod case _ ⇒ false } @@ -57,25 +71,19 @@ class LocalStringDefinitionTest extends PropertiesTest { */ private def isStringUsageAnnotation(a: Annotation): Boolean = // TODO: Is there a better way than string comparison? - a.annotationType.toJavaClass.getName == fqStringDefAnnotation - - describe("the org.opalj.fpcf.StringTrackingAnalysis is executed") { - val as = executeAnalyses(Set(LazyStringTrackingAnalysis)) - as.propertyStore.shutdown() - validateProperties(as, fieldsWithAnnotations(as.project), Set("StringDefinitions")) - } + a.annotationType.toJavaClass.getName == LocalStringDefinitionTest.fqStringDefAnnotation describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { - val cutPath = System.getProperty("user.dir")+ - "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/fixtures/string_definition/TestMethods.class" - val p = Project(new File(cutPath)) + val p = Project(getRelevantProjectFiles, Array[File]()) val ps = p.get(org.opalj.fpcf.PropertyStoreKey) ps.setupPhase(Set(StringConstancyProperty)) - LazyStringTrackingAnalysis.init(p, ps) - LazyStringTrackingAnalysis.schedule(ps, null) - val tacProvider = p.get(DefaultTACAIKey) + LazyStringDefinitionAnalysis.init(p, ps) + LazyStringDefinitionAnalysis.schedule(ps, null) + // We need a "method to entity" matching for the evaluation (see further below) + val m2e = mutable.HashMap[Method, (Entity, Method)]() + val tacProvider = p.get(DefaultTACAIKey) p.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || isStringUsageAnnotation(a) @@ -83,11 +91,31 @@ class LocalStringDefinitionTest extends PropertiesTest { } foreach { m ⇒ extractUVar(tacProvider(m).stmts) match { case Some(uvar) ⇒ - ps.force(Tuple2(uvar, m), StringConstancyProperty.key) - ps.waitOnPhaseCompletion() + val e = Tuple2(uvar, m) + ps.force(e, StringConstancyProperty.key) + m2e += (m → e) case _ ⇒ } } + + // As entity, we need not the method but a tuple (DUVar, Method), thus this transformation + val eas = methodsWithAnnotations(p).map { next ⇒ + Tuple3(m2e(next._1), next._2, next._3) + } + validateProperties( + TestContext(p, ps, Set(new LocalStringDefinitionAnalysis(p))), + eas, Set("StringConstancy") + ) + ps.waitOnPhaseCompletion() } } + +object LocalStringDefinitionTest { + + val fqStringDefAnnotation = "org.opalj.fpcf.properties.string_definition.StringDefinitions" + val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_definition.TestMethods" + // The name of the method from which to extract DUVars to analyze + val nameTestMethod = "analyzeString" + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 952d38d36a..3ddb400da0 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -3,30 +3,96 @@ package org.opalj.fpcf.properties.string_definition import org.opalj.br.analyses.Project import org.opalj.br.AnnotationLike +import org.opalj.br.ElementValue import org.opalj.br.ObjectType import org.opalj.fpcf.properties.AbstractPropertyMatcher import org.opalj.fpcf.Property +import org.opalj.fpcf.properties.StringConstancyProperty /** * Matches local variable's `StringConstancy` property. The match is successful if the - * variable has a constancy level that matches its usage and the expected values are present. + * variable has a constancy level that matches its actual usage and the expected values are present. * * @author Patrick Mell */ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { + /** + * @param a An annotation like of type + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * @return Returns the constancy level specified in the annotation as a string and `None` in + * case the element with the name 'expectedLevel' was not present in the annotation + * (should never be the case if an annotation of the correct type is passed). + */ + private def getConstancyLevel(a: AnnotationLike): Option[String] = { + a.elementValuePairs.find(_.name == "expectedLevel") match { + case Some(el) ⇒ Some(el.value.asEnumValue.constName) + case None ⇒ None + } + } + + /** + * @param a An annotation like of type + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * @return Returns an array of strings with the expected / possible string values and `None` in + * case the element with the name 'expectedValues' was not present in the annotation + * (should never be the case if an annotation of the correct type is passed). + */ + private def getPossibleStrings(a: AnnotationLike): Option[Array[String]] = { + a.elementValuePairs.find(_.name == "expectedValues") match { + case Some(el) ⇒ Some( + el.value.asArrayValue.values.map { f: ElementValue ⇒ f.asStringValue.value }.toArray + ) + case None ⇒ None + } + } + + private def aToMsg(a: AnnotationLike): String = { + val constancyLevel = getConstancyLevel(a).get.toLowerCase + val ps = getPossibleStrings(a).get.mkString("[", ", ", "]") + s"StringConstancyProperty { Constancy Level: $constancyLevel; Possible Strings: $ps }" + } + + /** + * @param a1 The first array. + * @param a2 The second array. + * @return Returns true if both arrays have the same length and all values of the first array + * are contained in the second array. + */ + private def doArraysContainTheSameValues(a1: Array[String], a2: Array[String]): Boolean = { + if (a1.length != a2.length) { + return false + } + a1.map(a2.contains(_)).forall { b ⇒ b } + } + /** * @inheritdoc */ override def validateProperty( - p: Project[_], - as: Set[ObjectType], - entity: Any, - a: AnnotationLike, - properties: Traversable[Property] - ): Option[String] = { + p: Project[_], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Traversable[Property] + ): Option[String] = { + val prop = properties.filter( + _.isInstanceOf[StringConstancyProperty] + ).head.asInstanceOf[StringConstancyProperty] + + val expLevel = getConstancyLevel(a).get + val actLevel = prop.constancyLevel.toString + if (expLevel.toLowerCase != actLevel.toLowerCase) { + return Some(aToMsg(a)) + } + + val expStrings = prop.possibleStrings.toArray + val actStrings = getPossibleStrings(a).get + if (!doArraysContainTheSameValues(expStrings, actStrings)) { + return Some(aToMsg(a)) + } + None - // TODO: Implement the matcher } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index f7a1e4394c..8e4b269e00 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -3,13 +3,15 @@ package org.opalj.fpcf.analyses.string_definition import org.opalj.br.analyses.SomeProject import org.opalj.fpcf.FPCFAnalysis -import org.opalj.fpcf.FPCFEagerAnalysisScheduler import org.opalj.fpcf.PropertyComputationResult import org.opalj.fpcf.PropertyKind import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.properties.StringConstancyLevel._ import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.fpcf.NoResult +import org.opalj.fpcf.ComputationSpecification import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore import org.opalj.tac.Assignment @@ -40,7 +42,7 @@ class StringTrackingAnalysisContext( * * @author Patrick Mell */ -class LocalStringDefinitionAnalysis private[analyses] ( +class LocalStringDefinitionAnalysis( val project: SomeProject ) extends FPCFAnalysis { @@ -50,8 +52,9 @@ class LocalStringDefinitionAnalysis private[analyses] ( // TODO: What is a better way to test if the given DUVar is of a certain type? val simpleClassName = data._1.value.getClass.getSimpleName simpleClassName match { - case "StringValue" ⇒ processStringValue(data) - case "SObjectValue" ⇒ processSObjectValue(data) + case "StringValue" ⇒ processStringValue(data) + case "MultipleReferenceValues" ⇒ processMultipleDefSites(data) + case "SObjectValue" ⇒ processSObjectValue(data) case _ ⇒ throw new IllegalArgumentException( s"cannot process given UVar type ($simpleClassName)" ) @@ -82,6 +85,14 @@ class LocalStringDefinitionAnalysis private[analyses] ( Result(data, StringConstancyProperty(level, assignedValues)) } + /** + * Processes the case that a UVar has multiple definition sites. + */ + private def processMultipleDefSites(data: P): PropertyComputationResult = { + // TODO: To be implemented + NoResult + } + /** * Processes the case that the UVar is of type `SObjectValue`. */ @@ -117,7 +128,7 @@ class LocalStringDefinitionAnalysis private[analyses] ( /** * Processes the case that a function call is involved, e.g., to StringBuilder#append. * - * @param stmts The surrounding context. For this analysis, the surrounding method. + * @param stmts The surrounding context. For this analysis, the surrounding method. * @param receiver Receiving object of the VirtualFunctionCall. * @return Returns a tuple with the constancy level and the string value after the function * call. @@ -150,8 +161,8 @@ class LocalStringDefinitionAnalysis private[analyses] ( * Determines the string value that was passed to a `StringBuilder#append` method. This function * can process string constants as well as function calls as argument to append. * - * @param call A function call of `StringBuilder#append`. Note that for all other methods an - * [[IllegalArgumentException]] will be thrown. + * @param call A function call of `StringBuilder#append`. Note that for all other methods an + * [[IllegalArgumentException]] will be thrown. * @param stmts The surrounding context. For this analysis, the surrounding method. * @return For constants strings as arguments, this function returns the string value and the * level [[CONSTANT]]. For function calls "*" (to indicate ''any @@ -175,24 +186,31 @@ class LocalStringDefinitionAnalysis private[analyses] ( } -/** - * Executor for the lazy analysis. - */ -object LazyStringTrackingAnalysis extends FPCFEagerAnalysisScheduler { - - final override def uses: Set[PropertyKind] = Set.empty +sealed trait LocalStringDefinitionAnalysisScheduler extends ComputationSpecification { final override def derives: Set[PropertyKind] = Set(StringConstancyProperty) - final override type InitializationData = Null + final override def uses: Set[PropertyKind] = { Set() } + final override type InitializationData = Null final def init(p: SomeProject, ps: PropertyStore): Null = null def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} def afterPhaseCompletion(p: SomeProject, ps: PropertyStore): Unit = {} - final override def start(p: SomeProject, ps: PropertyStore, unused: Null): FPCFAnalysis = { +} + +/** + * Executor for the lazy analysis. + */ +object LazyStringDefinitionAnalysis + extends LocalStringDefinitionAnalysisScheduler + with FPCFLazyAnalysisScheduler { + + final override def startLazily( + p: SomeProject, ps: PropertyStore, unused: Null + ): FPCFAnalysis = { val analysis = new LocalStringDefinitionAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index d17e1404b4..ddf4a23395 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -38,7 +38,7 @@ object StringConstancyLevel extends Enumeration { * appending another string, s2. Later, s2 might be removed partially or entirely without * violating the constraints of this level. */ - final val PARTIALLY_CONSTANT = Value("partially-constant") + final val PARTIALLY_CONSTANT = Value("partially_constant") /** * This level indicates that a string at some read operations has an unpredictable value. @@ -48,12 +48,17 @@ object StringConstancyLevel extends Enumeration { } class StringConstancyProperty( - val constancyLevel: StringConstancyLevel.Value, - val properties: ArrayBuffer[String] + val constancyLevel: StringConstancyLevel.Value, + val possibleStrings: ArrayBuffer[String] ) extends Property with StringConstancyPropertyMetaInformation { final def key = StringConstancyProperty.key + override def toString: String = { + val ps = possibleStrings.mkString("[", ", ", "]") + s"StringConstancyProperty { Constancy Level: $constancyLevel; Possible Strings: $ps }" + } + } object StringConstancyProperty extends StringConstancyPropertyMetaInformation { @@ -65,7 +70,7 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { PropertyKeyName, (_: PropertyStore, _: FallbackReason, e: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - StringConstancyProperty(DYNAMIC, ArrayBuffer()) + StringConstancyProperty(DYNAMIC, ArrayBuffer("*")) }, (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, (_: PropertyStore, _: Entity) ⇒ None @@ -73,7 +78,8 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { } def apply( - constancyLevel: StringConstancyLevel.Value, properties: ArrayBuffer[String] - ): StringConstancyProperty = new StringConstancyProperty(constancyLevel, properties) + constancyLevel: StringConstancyLevel.Value, + possibleStrings: ArrayBuffer[String] + ): StringConstancyProperty = new StringConstancyProperty(constancyLevel, possibleStrings) } From 9579c79a9cd6a51d8d5f4d2ba051bdc5c0da4d4e Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:40:50 +0100 Subject: [PATCH 005/316] Renamed and commented a function. --- .../LocalStringDefinitionMatcher.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 3ddb400da0..68c861906d 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -47,7 +47,15 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { } } - private def aToMsg(a: AnnotationLike): String = { + /** + * Takes an [[AnnotationLike]] which represents a [[StringConstancyProperty]] and returns its + * stringified representation. + * + * @param a The annotation. This function requires that it holds a StringConstancyProperty. + * @return The stringified representation, which is identical to + * [[StringConstancyProperty.toString]]. + */ + private def propertyAnnotation2Str(a: AnnotationLike): String = { val constancyLevel = getConstancyLevel(a).get.toLowerCase val ps = getPossibleStrings(a).get.mkString("[", ", ", "]") s"StringConstancyProperty { Constancy Level: $constancyLevel; Possible Strings: $ps }" @@ -83,13 +91,13 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { val expLevel = getConstancyLevel(a).get val actLevel = prop.constancyLevel.toString if (expLevel.toLowerCase != actLevel.toLowerCase) { - return Some(aToMsg(a)) + return Some(propertyAnnotation2Str(a)) } val expStrings = prop.possibleStrings.toArray val actStrings = getPossibleStrings(a).get if (!doArraysContainTheSameValues(expStrings, actStrings)) { - return Some(aToMsg(a)) + return Some(propertyAnnotation2Str(a)) } None From 905e02e2d2d6ff0cdb2e8a34ec53722559326303 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 17:57:42 +0100 Subject: [PATCH 006/316] Restructured the code for the StringDefinitionAnalysis. --- .../LocalStringDefinitionAnalysis.scala | 159 ++---------------- .../AbstractExprProcessor.scala | 35 ++++ .../expr_processing/ArrayLoadProcessor.scala | 46 +++++ .../expr_processing/ExprHandler.scala | 63 +++++++ .../NonVirtualFunctionCallProcessor.scala | 38 +++++ .../StringConstProcessor.scala | 34 ++++ .../VirtualFunctionCallProcessor.scala | 89 ++++++++++ 7 files changed, 316 insertions(+), 148 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 8e4b269e00..0773f4d5bc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -7,22 +7,11 @@ import org.opalj.fpcf.PropertyComputationResult import org.opalj.fpcf.PropertyKind import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result -import org.opalj.fpcf.properties.StringConstancyLevel._ import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FPCFLazyAnalysisScheduler -import org.opalj.fpcf.NoResult import org.opalj.fpcf.ComputationSpecification -import org.opalj.tac.ArrayLoad -import org.opalj.tac.ArrayStore -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.NonVirtualFunctionCall -import org.opalj.tac.SimpleTACAIKey +import org.opalj.fpcf.analyses.string_definition.expr_processing.ExprHandler import org.opalj.tac.Stmt -import org.opalj.tac.StringConst -import org.opalj.tac.VirtualFunctionCall - -import scala.collection.mutable.ArrayBuffer class StringTrackingAnalysisContext( val stmts: Array[Stmt[V]] @@ -46,141 +35,12 @@ class LocalStringDefinitionAnalysis( val project: SomeProject ) extends FPCFAnalysis { - def analyze( - data: P - ): PropertyComputationResult = { - // TODO: What is a better way to test if the given DUVar is of a certain type? - val simpleClassName = data._1.value.getClass.getSimpleName - simpleClassName match { - case "StringValue" ⇒ processStringValue(data) - case "MultipleReferenceValues" ⇒ processMultipleDefSites(data) - case "SObjectValue" ⇒ processSObjectValue(data) - case _ ⇒ throw new IllegalArgumentException( - s"cannot process given UVar type ($simpleClassName)" - ) - } - } - - /** - * Processes the case that the UVar is a string value. - */ - private def processStringValue(data: P): PropertyComputationResult = { - val tacProvider = p.get(SimpleTACAIKey) - val methodStmts = tacProvider(data._2).stmts - - val assignedValues = ArrayBuffer[String]() - val level = CONSTANT - - val defSites = data._1.definedBy - defSites.filter(_ >= 0).foreach(defSite ⇒ { - val Assignment(_, _, expr) = methodStmts(defSite) - expr match { - case s: StringConst ⇒ - assignedValues += s.value - // TODO: Non-constant strings are not taken into consideration; problem? - case _ ⇒ - } - }) - - Result(data, StringConstancyProperty(level, assignedValues)) - } - - /** - * Processes the case that a UVar has multiple definition sites. - */ - private def processMultipleDefSites(data: P): PropertyComputationResult = { - // TODO: To be implemented - NoResult - } - - /** - * Processes the case that the UVar is of type `SObjectValue`. - */ - private def processSObjectValue(data: P): PropertyComputationResult = { - val tacProvider = p.get(SimpleTACAIKey) - val stmts = tacProvider(data._2).stmts - - val defSite = data._1.definedBy.filter(_ >= 0) - // TODO: Consider case for more than one defSite? Example for that? - val expr = stmts(defSite.head).asAssignment.expr - expr match { - case _: NonVirtualFunctionCall[V] ⇒ - // Local analysis => no processing - Result(data, StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))) - - case VirtualFunctionCall(_, _, _, _, _, receiver, _) ⇒ - val intermResult = processVirtualFuncCall(stmts, receiver) - Result(data, StringConstancyProperty(intermResult._1, intermResult._2)) - - case ArrayLoad(_, _, arrRef) ⇒ - // For assignments which use arrays, determine all possible values - val arrDecl = stmts(arrRef.asVar.definedBy.head) - val arrValues = arrDecl.asAssignment.targetVar.usedBy.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } map { f: Int ⇒ - val defSite = stmts(f).asArrayStore.value.asVar.definedBy.head - stmts(defSite).asAssignment.expr.asStringConst.value - } - Result(data, StringConstancyProperty(CONSTANT, arrValues.to[ArrayBuffer])) - } - } - - /** - * Processes the case that a function call is involved, e.g., to StringBuilder#append. - * - * @param stmts The surrounding context. For this analysis, the surrounding method. - * @param receiver Receiving object of the VirtualFunctionCall. - * @return Returns a tuple with the constancy level and the string value after the function - * call. - */ - private def processVirtualFuncCall( - stmts: Array[Stmt[V]], receiver: Expr[V] - ): (StringConstancyLevel, ArrayBuffer[String]) = { - var level = CONSTANT - - // TODO: Are these long concatenations the best / most robust way? - val appendCall = - stmts(receiver.asVar.definedBy.head).asAssignment.expr.asVirtualFunctionCall - - // Get previous value of string builder - val baseAssignment = stmts(appendCall.receiver.asVar.definedBy.head).asAssignment - val baseStr = valueOfAppendCall(baseAssignment.expr.asVirtualFunctionCall, stmts) - var assignedStr = baseStr._1 - // Get appended value and build the new string value - val appendData = valueOfAppendCall(appendCall, stmts) - if (appendData._2 == CONSTANT) { - assignedStr += appendData._1 - } else { - assignedStr += "*" - level = PARTIALLY_CONSTANT - } - Tuple2(level, ArrayBuffer(assignedStr)) - } - - /** - * Determines the string value that was passed to a `StringBuilder#append` method. This function - * can process string constants as well as function calls as argument to append. - * - * @param call A function call of `StringBuilder#append`. Note that for all other methods an - * [[IllegalArgumentException]] will be thrown. - * @param stmts The surrounding context. For this analysis, the surrounding method. - * @return For constants strings as arguments, this function returns the string value and the - * level [[CONSTANT]]. For function calls "*" (to indicate ''any - * value'') and [[DYNAMIC]]. - */ - private def valueOfAppendCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] - ): (String, Value) = { - // TODO: Check the base object as well - if (call.name != "append") { - throw new IllegalArgumentException("can only process StringBuilder#append calls") - } - - val defAssignment = call.params.head.asVar.definedBy.head - val assignExpr = stmts(defAssignment).asAssignment.expr - assignExpr match { - case _: NonVirtualFunctionCall[V] ⇒ Tuple2("*", DYNAMIC) - case StringConst(_, value) ⇒ (value, CONSTANT) + def analyze(data: P): PropertyComputationResult = { + val exprHandler = ExprHandler(p, data._2) + val intermResults = data._1.definedBy.filter(_ >= 0).map(exprHandler.processDefSite _) + intermResults.head match { + case Some(property) ⇒ Result(data, property) + case None ⇒ throw new IllegalArgumentException("could not process expression") } } @@ -190,9 +50,12 @@ sealed trait LocalStringDefinitionAnalysisScheduler extends ComputationSpecifica final override def derives: Set[PropertyKind] = Set(StringConstancyProperty) - final override def uses: Set[PropertyKind] = { Set() } + final override def uses: Set[PropertyKind] = { + Set() + } final override type InitializationData = Null + final def init(p: SomeProject, ps: PropertyStore): Null = null def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala new file mode 100644 index 0000000000..5a9d3b31fc --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing + +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.Expr +import org.opalj.tac.Stmt + +/** + * AbstractExprProcessor defines the abstract / general strategy to process expressions in the + * context of string definition analyses. Different sub-classes process different kinds of + * expressions. The idea is to transform expressions into [[StringConstancyProperty]] objects. For + * example, the expression of a constant assignment might be processed. + * + * @author Patrick Mell + */ +abstract class AbstractExprProcessor() { + + /** + * Implementations process an expression which is supposed to yield (not necessarily fixed) a + * string value. + * + * @param stmts The statements that surround the expression to process, such as a method. + * Concrete processors might use these to retrieve further information. + * @param expr The expression to process. Make sure that the expression, which is passed, meets + * the requirements of that implementation. + * @return Determines the [[org.opalj.fpcf.properties.StringConstancyLevel]] as well as possible + * string values that the expression might produce. If `expr` does not meet the + * requirements of a an implementation, `None` will be returned. + * For further details, see [[StringConstancyProperty]]. + * @see StringConstancyProperty + */ + def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala new file mode 100644 index 0000000000..36afadd1ba --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -0,0 +1,46 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.tac.Expr +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyLevel +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.ArrayLoad +import org.opalj.tac.ArrayStore +import org.opalj.tac.Stmt + +import scala.collection.mutable.ArrayBuffer + +/** + * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.ArrayLoad]] + * expressions. + * + * @author Patrick Mell + */ +class ArrayLoadProcessor() extends AbstractExprProcessor { + + /** + * `expr` is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise `None` will be + * returned). + * + * @see [[AbstractExprProcessor#process]] + */ + override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = { + expr match { + case al: ArrayLoad[V] ⇒ + val arrRef = al.arrayRef + val arrDecl = stmts(arrRef.asVar.definedBy.head) + val arrValues = arrDecl.asAssignment.targetVar.usedBy.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } map { f: Int ⇒ + val defSite = stmts(f).asArrayStore.value.asVar.definedBy.head + stmts(defSite).asAssignment.expr.asStringConst.value + } + + Some(StringConstancyProperty( + StringConstancyLevel.CONSTANT, arrValues.to[ArrayBuffer] + )) + case _ ⇒ None + } + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala new file mode 100644 index 0000000000..7525737bc3 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -0,0 +1,63 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.ArrayLoad +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.StringConst +import org.opalj.tac.VirtualFunctionCall + +/** + * `ExprHandler` is responsible for processing expressions that are relevant in order to determine + * which value(s) a string read operation might have. These expressions usually come from the + * definitions sites of the variable of interest. + * + * @param p The project associated with the analysis. + * @param m The [[Method]] in which the read statement of the string variable of interest occurred. + * + * @author Patrick Mell + */ +class ExprHandler(p: SomeProject, m: Method) { + + private val tacProvider = p.get(SimpleTACAIKey) + private val methodStmts = tacProvider(m).stmts + + /** + * Processes a given definition site. That is, this function determines the + * [[StringConstancyProperty]] of a string definition. + * + * @param defSite The definition site to process. Make sure that (1) the value is >= 0, (2) it + * actually exists, and (3) contains an Assignment whose expression is of a type + * that is supported by a sub-class of [[AbstractExprProcessor]]. + * @return Returns an instance of [[StringConstancyProperty]] that describes the definition + * at the specified site. In case the rules listed above or the ones of the different + * processors are not met `None` will be returned.. + */ + def processDefSite(defSite: Int): Option[StringConstancyProperty] = { + val expr = methodStmts(defSite).asAssignment.expr + val exprProcessor: AbstractExprProcessor = expr match { + case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor() + case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor() + case _: NonVirtualFunctionCall[V] ⇒ new NonVirtualFunctionCallProcessor() + case _: StringConst ⇒ new StringConstProcessor() + case _ ⇒ throw new IllegalArgumentException( + s"cannot process expression $expr" + ) + } + exprProcessor.process(methodStmts, expr) + } + +} + +object ExprHandler { + + /** + * @see [[ExprHandler]] + */ + def apply(p: SomeProject, m: Method): ExprHandler = new ExprHandler(p, m) + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala new file mode 100644 index 0000000000..8adc7ce3ac --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -0,0 +1,38 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.tac.Expr +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.Stmt + +import scala.collection.mutable.ArrayBuffer + +/** + * This implementation of [[AbstractExprProcessor]] processes + * [[org.opalj.tac.NonVirtualFunctionCall]] expressions. + * Currently, this implementation is only a rough approximation in the sense that all + * `NonVirtualFunctionCall`s are processed by returning + * `StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))`, i.e., do not analyze the function call in + * depth. + * + * @author Patrick Mell + */ +class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { + + /** + * `expr` is required to be of type [[org.opalj.tac.NonVirtualFunctionCall]] (otherwise `None` + * will be returned). + * `stmts` currently is not relevant, thus an empty array may be passed. + * + * @see [[AbstractExprProcessor#process]] + */ + override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = + expr match { + case _: NonVirtualFunctionCall[V] ⇒ + Some(StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))) + case _ ⇒ None + } + +} \ No newline at end of file diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala new file mode 100644 index 0000000000..d7e149364f --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala @@ -0,0 +1,34 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyLevel +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.Expr +import org.opalj.tac.Stmt +import org.opalj.tac.StringConst + +import scala.collection.mutable.ArrayBuffer + +/** + * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.StringConst]] + * expressions. + * + * @author Patrick Mell + */ +class StringConstProcessor() extends AbstractExprProcessor { + + /** + * For this implementation, `stmts` is not needed (thus, you may pass an empty Array). `expr` is + * required to be of type [[org.opalj.tac.StringConst]] (otherwise `None` will be returned). + * + * @see [[AbstractExprProcessor.process]] + */ + override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = + expr match { + case strConst: StringConst ⇒ Some(StringConstancyProperty( + StringConstancyLevel.CONSTANT, ArrayBuffer(strConst.value) + )) + case _ ⇒ None + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala new file mode 100644 index 0000000000..379639efe5 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -0,0 +1,89 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.tac.Expr +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyLevel.CONSTANT +import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.properties.StringConstancyLevel.PARTIALLY_CONSTANT +import org.opalj.fpcf.properties.StringConstancyLevel.StringConstancyLevel +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.Stmt +import org.opalj.tac.StringConst +import org.opalj.tac.VirtualFunctionCall + +import scala.collection.mutable.ArrayBuffer + +/** + * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.VirtualFunctionCall]] + * expressions. + * Currently, [[VirtualFunctionCallProcessor]] (only) aims at processing calls of + * [[StringBuilder#append]]. + * + * @author Patrick Mell + */ +class VirtualFunctionCallProcessor() extends AbstractExprProcessor { + + /** + * `expr` is required to be of type [[org.opalj.tac.VirtualFunctionCall]] (otherwise `None` will + * be returned). + * + * @see [[AbstractExprProcessor.process]] + */ + override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = { + var level = CONSTANT + + expr match { + case vfc: VirtualFunctionCall[V] ⇒ + val receiver = vfc.receiver + // TODO: Are these long concatenations the best / most robust way? + val appendCall = + stmts(receiver.asVar.definedBy.head).asAssignment.expr.asVirtualFunctionCall + + // Get previous value of string builder + val baseAssignment = stmts(appendCall.receiver.asVar.definedBy.head).asAssignment + val baseStr = valueOfAppendCall(baseAssignment.expr.asVirtualFunctionCall, stmts) + var assignedStr = baseStr._1 + // Get appended value and build the new string value + val appendData = valueOfAppendCall(appendCall, stmts) + if (appendData._2 == CONSTANT) { + assignedStr += appendData._1 + } else { + assignedStr += "*" + level = PARTIALLY_CONSTANT + } + + Some(StringConstancyProperty(level, ArrayBuffer(assignedStr))) + case _ ⇒ None + } + } + + /** + * Determines the string value that was passed to a `StringBuilder#append` method. This function + * can process string constants as well as function calls as argument to append. + * + * @param call A function call of `StringBuilder#append`. Note that for all other methods an + * [[IllegalArgumentException]] will be thrown. + * @param stmts The surrounding context, e.g., the surrounding method. + * @return For constants strings as arguments, this function returns the string value and the + * level [[org.opalj.fpcf.properties.StringConstancyLevel.CONSTANT]]. For function calls + * "*" (to indicate ''any value'') and + * [[org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC]]. + */ + private def valueOfAppendCall( + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + ): (String, StringConstancyLevel) = { + // TODO: Check the base object as well + if (call.name != "append") { + throw new IllegalArgumentException("can only process StringBuilder#append calls") + } + + val defAssignment = call.params.head.asVar.definedBy.head + val assignExpr = stmts(defAssignment).asAssignment.expr + assignExpr match { + case _: NonVirtualFunctionCall[V] ⇒ Tuple2("*", DYNAMIC) + case StringConst(_, value) ⇒ (value, CONSTANT) + } + } + +} From 0a731ab77837ee1f570a78959d489654f0a1e8d5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 18:03:40 +0100 Subject: [PATCH 007/316] The StringDefinitionAnalysis can now handle multiple definition sites. --- .../string_definition/TestMethods.java | 49 +++++++++++++++++++ .../LocalStringDefinitionAnalysis.scala | 10 ++-- .../expr_processing/ArrayLoadProcessor.scala | 32 +++++++----- .../expr_processing/ExprHandler.scala | 26 ++++++++-- .../VirtualFunctionCallProcessor.scala | 41 +++++++++------- .../properties/StringConstancyProperty.scala | 33 +++++++++++++ 6 files changed, 152 insertions(+), 39 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 618d00c7cb..dca846981b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -83,6 +83,55 @@ public void fromStringArray(int index) { } } + @StringDefinitions( + value = "a simple case where multiple definition sites have to be considered", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedValues = { "java.lang.System", "java.lang.Runtime" } + ) + public void multipleConstantDefSites(boolean cond) { + String s; + if (cond) { + s = "java.lang.System"; + } else { + s = "java.lang.Runtime"; + } + analyzeString(s); + } + + @StringDefinitions( + value = "a more comprehensive case where multiple definition sites have to be " + + "considered each with a different string generation mechanism", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedValues = { "java.lang.Object", "*", "java.lang.System", "java.lang.*" } + ) + public void multipleDefSites(int value) { + String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; + + String s; + switch (value) { + case 0: + s = arr[value]; + break; + case 1: + s = arr[value]; + break; + case 3: + s = "java.lang.System"; + break; + case 4: + s = "java.lang." + getSimpleStringBuilderClassName(); + break; + default: + s = getStringBuilderClassName(); + } + + analyzeString(s); + } + + private String getRuntimeClassName() { + return "java.lang.Runtime"; + } + private String getStringBuilderClassName() { return "java.lang.StringBuilder"; } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 0773f4d5bc..ec4d257489 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -36,11 +36,11 @@ class LocalStringDefinitionAnalysis( ) extends FPCFAnalysis { def analyze(data: P): PropertyComputationResult = { - val exprHandler = ExprHandler(p, data._2) - val intermResults = data._1.definedBy.filter(_ >= 0).map(exprHandler.processDefSite _) - intermResults.head match { - case Some(property) ⇒ Result(data, property) - case None ⇒ throw new IllegalArgumentException("could not process expression") + val properties = ExprHandler(p, data._2).processDefinitionSites(data._1.definedBy) + if (properties.isEmpty) { + throw new IllegalArgumentException("could not process expression(s)") + } else { + Result(data, StringConstancyProperty.reduce(properties).get) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index 36afadd1ba..768b525da9 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -2,7 +2,6 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.tac.Expr import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyLevel import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore @@ -14,31 +13,38 @@ import scala.collection.mutable.ArrayBuffer * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.ArrayLoad]] * expressions. * + * @param exprHandler As this expression processor will encounter other expressions outside its + * scope, such as StringConst or NonVirtualFunctionCall, an [[ExprHandler]] is + * required. + * * @author Patrick Mell */ -class ArrayLoadProcessor() extends AbstractExprProcessor { +class ArrayLoadProcessor( + private val exprHandler: ExprHandler +) extends AbstractExprProcessor { /** * `expr` is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise `None` will be * returned). * - * @see [[AbstractExprProcessor#process]] + * @see [[AbstractExprProcessor.process]] */ override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = { expr match { case al: ArrayLoad[V] ⇒ - val arrRef = al.arrayRef - val arrDecl = stmts(arrRef.asVar.definedBy.head) - val arrValues = arrDecl.asAssignment.targetVar.usedBy.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } map { f: Int ⇒ - val defSite = stmts(f).asArrayStore.value.asVar.definedBy.head - stmts(defSite).asAssignment.expr.asStringConst.value + val properties = ArrayBuffer[StringConstancyProperty]() + al.arrayRef.asVar.definedBy.foreach { defSite ⇒ + val arrDecl = stmts(defSite) + arrDecl.asAssignment.targetVar.usedBy.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + properties.appendAll(exprHandler.processDefinitionSites( + stmts(f).asArrayStore.value.asVar.definedBy + )) + } } - Some(StringConstancyProperty( - StringConstancyLevel.CONSTANT, arrValues.to[ArrayBuffer] - )) + StringConstancyProperty.reduce(properties.toArray) case _ ⇒ None } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 7525737bc3..9b2642d61b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -3,6 +3,7 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.Method import org.opalj.br.analyses.SomeProject +import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.ArrayLoad @@ -18,7 +19,6 @@ import org.opalj.tac.VirtualFunctionCall * * @param p The project associated with the analysis. * @param m The [[Method]] in which the read statement of the string variable of interest occurred. - * * @author Patrick Mell */ class ExprHandler(p: SomeProject, m: Method) { @@ -35,12 +35,16 @@ class ExprHandler(p: SomeProject, m: Method) { * that is supported by a sub-class of [[AbstractExprProcessor]]. * @return Returns an instance of [[StringConstancyProperty]] that describes the definition * at the specified site. In case the rules listed above or the ones of the different - * processors are not met `None` will be returned.. + * processors are not met `None` will be returned. */ def processDefSite(defSite: Int): Option[StringConstancyProperty] = { + if (defSite < 0) { + return None + } + val expr = methodStmts(defSite).asAssignment.expr val exprProcessor: AbstractExprProcessor = expr match { - case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor() + case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor(this) case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor() case _: NonVirtualFunctionCall[V] ⇒ new NonVirtualFunctionCallProcessor() case _: StringConst ⇒ new StringConstProcessor() @@ -51,6 +55,22 @@ class ExprHandler(p: SomeProject, m: Method) { exprProcessor.process(methodStmts, expr) } + /** + * This function serves as a wrapper function for [[ExprHandler.processDefSite]] in the + * sense that it processes multiple definition sites. Thus, it may throw an exception as well if + * an expression referenced by a definition site cannot be processed. The same rules as for + * [[ExprHandler.processDefSite]] apply. + * + * @param defSites The definition sites to process. + * @return Returns an array of [[StringConstancyProperty]] elements. In contrast to + * [[ExprHandler.processDefSite]] this function returns only those values that are not + * equals `None`. Furthermore, note that this function returns the values unmodified, + * e.g., no call to [[StringConstancyProperty#reduce]] whatsoever is executed that could + * change the array. + */ + def processDefinitionSites(defSites: IntTrieSet): Array[StringConstancyProperty] = + defSites.filter(_ >= 0).map(processDefSite _).filter(_.isDefined).map(_.get).toArray + } object ExprHandler { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index 379639efe5..58fcf2aeb3 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -31,29 +31,34 @@ class VirtualFunctionCallProcessor() extends AbstractExprProcessor { * @see [[AbstractExprProcessor.process]] */ override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = { - var level = CONSTANT + val properties = ArrayBuffer[StringConstancyProperty]() expr match { case vfc: VirtualFunctionCall[V] ⇒ - val receiver = vfc.receiver // TODO: Are these long concatenations the best / most robust way? - val appendCall = - stmts(receiver.asVar.definedBy.head).asAssignment.expr.asVirtualFunctionCall - - // Get previous value of string builder - val baseAssignment = stmts(appendCall.receiver.asVar.definedBy.head).asAssignment - val baseStr = valueOfAppendCall(baseAssignment.expr.asVirtualFunctionCall, stmts) - var assignedStr = baseStr._1 - // Get appended value and build the new string value - val appendData = valueOfAppendCall(appendCall, stmts) - if (appendData._2 == CONSTANT) { - assignedStr += appendData._1 - } else { - assignedStr += "*" - level = PARTIALLY_CONSTANT + vfc.receiver.asVar.definedBy.foreach { defSite ⇒ + val appendCall = stmts(defSite).asAssignment.expr.asVirtualFunctionCall + appendCall.receiver.asVar.definedBy.foreach { rDefSite ⇒ + var level = CONSTANT + // Get previous value of string builder + val baseAssignment = stmts(rDefSite).asAssignment + val baseStr = valueOfAppendCall( + baseAssignment.expr.asVirtualFunctionCall, stmts + ) + var assignedStr = baseStr._1 + // Get appended value and build the new string value + val appendData = valueOfAppendCall(appendCall, stmts) + if (appendData._2 == CONSTANT) { + assignedStr += appendData._1 + } else { + assignedStr += "*" + level = PARTIALLY_CONSTANT + } + properties.append(StringConstancyProperty(level, ArrayBuffer(assignedStr))) + } } + StringConstancyProperty.reduce(properties.toArray) - Some(StringConstancyProperty(level, ArrayBuffer(assignedStr))) case _ ⇒ None } } @@ -86,4 +91,4 @@ class VirtualFunctionCallProcessor() extends AbstractExprProcessor { } } -} +} \ No newline at end of file diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index ddf4a23395..9af4ec9ac1 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -9,7 +9,9 @@ import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.properties.StringConstancyLevel.CONSTANT import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.properties.StringConstancyLevel.PARTIALLY_CONSTANT import scala.collection.mutable.ArrayBuffer @@ -82,4 +84,35 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { possibleStrings: ArrayBuffer[String] ): StringConstancyProperty = new StringConstancyProperty(constancyLevel, possibleStrings) + /** + * This function takes an array of [[StringConstancyProperty]] and reduces it. This means that + * the most-general [[StringConstancyLevel]] encountered in the given properties is returned + * ([[StringConstancyLevel.CONSTANT]] is the most static > + * [[StringConstancyLevel.PARTIALLY_CONSTANT]] > [[StringConstancyLevel.DYNAMIC]] is the most- + * general) along with the union of all possible strings. Note that this union contains every + * possible string only once (also the "*" marker)! "*" might be contained as well as (semi) + * constant strings to convey all possibilities. + * + * @param properties The properties to reduce. + * @return Returns a single [[StringConstancyProperty]] with values as described above. In case + * the given `properties` array is empty, `None` will be returned. + */ + def reduce(properties: Array[StringConstancyProperty]): Option[StringConstancyProperty] = { + if (properties.isEmpty) { + return None + } + + val possibleValues = ArrayBuffer[String]() + var level = CONSTANT + properties.foreach { next ⇒ + if ((level == CONSTANT) || + (level == PARTIALLY_CONSTANT && next.constancyLevel != CONSTANT)) { + level = next.constancyLevel + } + possibleValues.appendAll(next.possibleStrings) + } + + Some(StringConstancyProperty(level, possibleValues.distinct)) + } + } From 6a00c3e8520c0975fe6c57078bd46a4b60dd7926 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 18:12:07 +0100 Subject: [PATCH 008/316] The StringDefinitionAnalysis was changed in a way that it now builds string trees. --- .../string_definition/TestMethods.java | 147 +++++++++++++----- .../string_definition/StringDefinitions.java | 9 +- .../LocalStringDefinitionMatcher.scala | 45 ++---- .../LocalStringDefinitionAnalysis.scala | 36 ++++- .../AbstractExprProcessor.scala | 22 +-- .../expr_processing/ArrayLoadProcessor.scala | 31 ++-- .../expr_processing/ExprHandler.scala | 93 ++++++++--- .../NewStringBuilderProcessor.scala | 63 ++++++++ .../NonVirtualFunctionCallProcessor.scala | 30 ++-- .../StringConstProcessor.scala | 26 ++-- .../VirtualFunctionCallProcessor.scala | 129 ++++++++------- .../properties/StringConstancyProperty.scala | 92 ++--------- .../StringConstancyInformation.scala | 16 ++ .../properties/StringConstancyLevel.scala | 53 +++++++ .../properties/StringTree.scala | 135 ++++++++++++++++ .../properties/package.scala | 12 ++ 16 files changed, 664 insertions(+), 275 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index dca846981b..aeda05f9ca 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -21,32 +21,67 @@ public class TestMethods { public void analyzeString(String s) { } + // The following is a strange case (difficult / impossible? to follow back information flow) + // @StringDefinitions( + // value = "checks if a string value with > 1 continuous appends is determined correctly", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "java.lang.String" + // ) + // public void directAppendConcat() { + // StringBuilder sb = new StringBuilder("java"); + // sb.append(".").append("lang").append(".").append("String"); + // analyzeString(sb.toString()); + // } + @StringDefinitions( value = "read-only string, trivial case", expectedLevel = StringConstancyLevel.CONSTANT, - expectedValues = { "java.lang.String" } + expectedStrings = "java.lang.String" ) public void constantString() { + analyzeString("java.lang.String"); + } + + @StringDefinitions( + value = "read-only string variable, trivial case", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "java.lang.String" + ) + public void constantStringVariable() { String className = "java.lang.String"; analyzeString(className); } @StringDefinitions( - value = "checks if the string value for the *forName* call is correctly determined", + value = "checks if a string value with one append is determined correctly", expectedLevel = StringConstancyLevel.CONSTANT, - expectedValues = { "java.lang.string" } + expectedStrings = "java.lang.string" ) - public void stringConcatenation() { + public void simpleStringConcat() { String className = "java.lang."; System.out.println(className); className += "string"; analyzeString(className); } + @StringDefinitions( + value = "checks if a string value with > 1 appends is determined correctly", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "java.lang.string" + ) + public void advStringConcat() { + String className = "java."; + System.out.println(className); + className += "lang."; + System.out.println(className); + className += "string"; + analyzeString(className); + } + @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedValues = { "*" } + expectedStrings = "*" ) public void fromFunctionCall() { String className = getStringBuilderClassName(); @@ -56,7 +91,7 @@ public void fromFunctionCall() { @StringDefinitions( value = "constant string + string from function call => PARTIALLY_CONSTANT", expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedValues = { "java.lang.*" } + expectedStrings = "java.lang.*" ) public void fromConstantAndFunctionCall() { String className = "java.lang."; @@ -68,10 +103,8 @@ public void fromConstantAndFunctionCall() { @StringDefinitions( value = "array access with unknown index", expectedLevel = StringConstancyLevel.CONSTANT, - expectedValues = { - "java.lang.String", "java.lang.StringBuilder", - "java.lang.System", "java.lang.Runnable" - } + expectedStrings = "(java.lang.String | java.lang.System | " + + "java.lang.Runnable | java.lang.StringBuilder)" ) public void fromStringArray(int index) { String[] classes = { @@ -86,7 +119,7 @@ public void fromStringArray(int index) { @StringDefinitions( value = "a simple case where multiple definition sites have to be considered", expectedLevel = StringConstancyLevel.CONSTANT, - expectedValues = { "java.lang.System", "java.lang.Runtime" } + expectedStrings = "(java.lang.System | java.lang.Runtime)" ) public void multipleConstantDefSites(boolean cond) { String s; @@ -98,35 +131,69 @@ public void multipleConstantDefSites(boolean cond) { analyzeString(s); } - @StringDefinitions( - value = "a more comprehensive case where multiple definition sites have to be " - + "considered each with a different string generation mechanism", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedValues = { "java.lang.Object", "*", "java.lang.System", "java.lang.*" } - ) - public void multipleDefSites(int value) { - String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; - - String s; - switch (value) { - case 0: - s = arr[value]; - break; - case 1: - s = arr[value]; - break; - case 3: - s = "java.lang.System"; - break; - case 4: - s = "java.lang." + getSimpleStringBuilderClassName(); - break; - default: - s = getStringBuilderClassName(); - } - - analyzeString(s); - } + // @StringDefinitions( + // value = "a more comprehensive case where multiple definition sites have to be " + // + "considered each with a different string generation mechanism", + // expectedLevel = StringConstancyLevel.DYNAMIC, + // expectedStrings = "(java.lang.Object | * | java.lang.System | java.lang.*)" + // ) + // public void multipleDefSites(int value) { + // String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; + // + // String s; + // switch (value) { + // case 0: + // s = arr[value]; + // break; + // case 1: + // s = arr[value]; + // break; + // case 3: + // s = "java.lang.System"; + // break; + // case 4: + // s = "java.lang." + getSimpleStringBuilderClassName(); + // break; + // default: + // s = getStringBuilderClassName(); + // } + // + // analyzeString(s); + // } + + // @StringDefinitions( + // value = "if-else control structure which append to a string builder", + // expectedLevel = StringConstancyLevel.DYNAMIC, + // expectedStrings = "x | [Int Value]" + // ) + // public void ifElseWithStringBuilder() { + // StringBuilder sb = new StringBuilder(); + // int i = new Random().nextInt(); + // if (i % 2 == 0) { + // sb.append("x"); + // } else { + // sb.append(i + 1); + // } + // analyzeString(sb.toString()); + // } + + // @StringDefinitions( + // value = "if-else control structure within a for loop with known loop bounds", + // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // expectedValues = { "(\"x\" | [Int Value])^20" } + // ) + // public void ifElseInLoopWithKnownBounds() { + // StringBuilder sb = new StringBuilder(); + // for (int i = 0; i < 20; i++) { + // if (i % 2 == 0) { + // sb.append("x"); + // } else { + // sb.append(i + 1); + // } + // } + // + // analyzeString(sb.toString()); + // } private String getRuntimeClassName() { return "java.lang.Runtime"; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index d87bcb7dba..ed1d701d8e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -35,10 +35,11 @@ StringConstancyLevel expectedLevel() default StringConstancyLevel.DYNAMIC; /** - * A set of string elements that are expected. If exact matching is desired, insert only one - * element. Otherwise, a super set may be specified, e.g., if some value from an array is - * expected. + * A regexp like string that describes the elements that are expected. For the rules, refer to + * {@link org.opalj.fpcf.string_definition.properties.TreeElement}. + * For example, "(* | (hello | world)^5)" describes a string which can 1) either be any string + * or 2) a five time concatenation of "hello" and/or "world". */ - String[] expectedValues() default ""; + String expectedStrings() default ""; } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 68c861906d..ea5b21b333 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -3,7 +3,6 @@ package org.opalj.fpcf.properties.string_definition import org.opalj.br.analyses.Project import org.opalj.br.AnnotationLike -import org.opalj.br.ElementValue import org.opalj.br.ObjectType import org.opalj.fpcf.properties.AbstractPropertyMatcher import org.opalj.fpcf.Property @@ -34,46 +33,31 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { /** * @param a An annotation like of type * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. - * @return Returns an array of strings with the expected / possible string values and `None` in - * case the element with the name 'expectedValues' was not present in the annotation - * (should never be the case if an annotation of the correct type is passed). + * @return Returns the ''expectedStrings'' value from the annotation or `None` in case the + * element with the name ''expectedStrings'' was not present in the annotation (should + * never be the case if an annotation of the correct type is passed). */ - private def getPossibleStrings(a: AnnotationLike): Option[Array[String]] = { - a.elementValuePairs.find(_.name == "expectedValues") match { - case Some(el) ⇒ Some( - el.value.asArrayValue.values.map { f: ElementValue ⇒ f.asStringValue.value }.toArray - ) - case None ⇒ None + private def getExpectedStrings(a: AnnotationLike): Option[String] = { + a.elementValuePairs.find(_.name == "expectedStrings") match { + case Some(el) ⇒ Some(el.value.asStringValue.value) + case None ⇒ None } } /** - * Takes an [[AnnotationLike]] which represents a [[StringConstancyProperty]] and returns its + * Takes an [[AnnotationLike]] which represents a [[org.opalj.fpcf.properties.StringConstancyProperty]] and returns its * stringified representation. * * @param a The annotation. This function requires that it holds a StringConstancyProperty. * @return The stringified representation, which is identical to - * [[StringConstancyProperty.toString]]. + * [[org.opalj.fpcf.properties.StringConstancyProperty.toString]]. */ private def propertyAnnotation2Str(a: AnnotationLike): String = { val constancyLevel = getConstancyLevel(a).get.toLowerCase - val ps = getPossibleStrings(a).get.mkString("[", ", ", "]") + val ps = getExpectedStrings(a).get s"StringConstancyProperty { Constancy Level: $constancyLevel; Possible Strings: $ps }" } - /** - * @param a1 The first array. - * @param a2 The second array. - * @return Returns true if both arrays have the same length and all values of the first array - * are contained in the second array. - */ - private def doArraysContainTheSameValues(a1: Array[String], a2: Array[String]): Boolean = { - if (a1.length != a2.length) { - return false - } - a1.map(a2.contains(_)).forall { b ⇒ b } - } - /** * @inheritdoc */ @@ -87,16 +71,17 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { val prop = properties.filter( _.isInstanceOf[StringConstancyProperty] ).head.asInstanceOf[StringConstancyProperty] + val reducedProp = prop.stringTree.reduce() val expLevel = getConstancyLevel(a).get - val actLevel = prop.constancyLevel.toString + val actLevel = reducedProp.constancyLevel.toString if (expLevel.toLowerCase != actLevel.toLowerCase) { return Some(propertyAnnotation2Str(a)) } - val expStrings = prop.possibleStrings.toArray - val actStrings = getPossibleStrings(a).get - if (!doArraysContainTheSameValues(expStrings, actStrings)) { + // TODO: This string comparison is not very robust + val expStrings = getExpectedStrings(a).get + if (expStrings != reducedProp.possibleStrings) { return Some(propertyAnnotation2Str(a)) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index ec4d257489..075ad6dfb5 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -11,8 +11,13 @@ import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.analyses.string_definition.expr_processing.ExprHandler +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt +import scala.collection.mutable.ArrayBuffer + class StringTrackingAnalysisContext( val stmts: Array[Stmt[V]] ) @@ -36,11 +41,32 @@ class LocalStringDefinitionAnalysis( ) extends FPCFAnalysis { def analyze(data: P): PropertyComputationResult = { - val properties = ExprHandler(p, data._2).processDefinitionSites(data._1.definedBy) - if (properties.isEmpty) { - throw new IllegalArgumentException("could not process expression(s)") - } else { - Result(data, StringConstancyProperty.reduce(properties).get) + val tacProvider = p.get(SimpleTACAIKey) + val stmts = tacProvider(data._2).stmts + + val exprHandler = ExprHandler(p, data._2) + val defSites = data._1.definedBy + if (ExprHandler.isStringBuilderToStringCall(stmts(defSites.head).asAssignment)) { + val subtrees = ArrayBuffer[StringTree]() + defSites.foreach { nextDefSite ⇒ + val treeElements = ExprHandler.getDefSitesOfToStringReceiver( + stmts(nextDefSite).asAssignment + ).map { exprHandler.processDefSite _ }.filter(_.isDefined).map { _.get } + if (treeElements.size == 1) { + subtrees.append(treeElements.head) + } else { + subtrees.append(TreeConditionalElement(treeElements.toList)) + } + } + + val finalTree = if (subtrees.size == 1) subtrees.head else + TreeConditionalElement(subtrees.toList) + Result(data, StringConstancyProperty(finalTree)) + } // If not a call to StringBuilder.toString, then we deal with pure strings + else { + Result(data, StringConstancyProperty( + exprHandler.processDefSites(data._1.definedBy).get + )) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala index 5a9d3b31fc..73c626b3ba 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala @@ -2,15 +2,15 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.tac.Expr +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.tac.Assignment import org.opalj.tac.Stmt /** * AbstractExprProcessor defines the abstract / general strategy to process expressions in the * context of string definition analyses. Different sub-classes process different kinds of - * expressions. The idea is to transform expressions into [[StringConstancyProperty]] objects. For - * example, the expression of a constant assignment might be processed. + * expressions. The idea is to transform expressions into [[StringTree]] objects. For example, the + * expression of a constant assignment might be processed. * * @author Patrick Mell */ @@ -20,16 +20,16 @@ abstract class AbstractExprProcessor() { * Implementations process an expression which is supposed to yield (not necessarily fixed) a * string value. * + * @param assignment The Assignment to process. Make sure that the assignment, which is + * passed, meets the requirements of that implementation. * @param stmts The statements that surround the expression to process, such as a method. * Concrete processors might use these to retrieve further information. - * @param expr The expression to process. Make sure that the expression, which is passed, meets - * the requirements of that implementation. - * @return Determines the [[org.opalj.fpcf.properties.StringConstancyLevel]] as well as possible - * string values that the expression might produce. If `expr` does not meet the - * requirements of a an implementation, `None` will be returned. - * For further details, see [[StringConstancyProperty]]. + * @return Determines the [[StringTree]] for the given `expr` and `stmts` from which as possible + * string values, which the expression might produce, can be derived. If `expr` does not + * meet the requirements of a an implementation, `None` will be returned (or in severe + * cases an exception be thrown). * @see StringConstancyProperty */ - def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] + def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index 768b525da9..4746633cae 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -1,13 +1,15 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing -import org.opalj.tac.Expr import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.fpcf.string_definition.properties.TreeElement import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore +import org.opalj.tac.Assignment import org.opalj.tac.Stmt -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.ListBuffer /** * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.ArrayLoad]] @@ -24,27 +26,32 @@ class ArrayLoadProcessor( ) extends AbstractExprProcessor { /** - * `expr` is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise `None` will be - * returned). + * The `expr` of `assignment`is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise + * `None` will be returned). * * @see [[AbstractExprProcessor.process]] */ - override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = { - expr match { + override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = { + assignment.expr match { case al: ArrayLoad[V] ⇒ - val properties = ArrayBuffer[StringConstancyProperty]() + val children = ListBuffer[TreeElement]() + // Loop over all possible array values al.arrayRef.asVar.definedBy.foreach { defSite ⇒ val arrDecl = stmts(defSite) arrDecl.asAssignment.targetVar.usedBy.filter { stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ - properties.appendAll(exprHandler.processDefinitionSites( - stmts(f).asArrayStore.value.asVar.definedBy - )) + // Actually, definedBy should contain only one element but for the sake of + // completion, loop over all + // TODO: If not, the tree construction has to be modified + val arrValues = stmts(f).asArrayStore.value.asVar.definedBy.map { + exprHandler.processDefSite _ + }.filter(_.isDefined).map(_.get) + children.appendAll(arrValues) } } - StringConstancyProperty.reduce(properties.toArray) + Some(TreeConditionalElement(children.toList)) case _ ⇒ None } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 9b2642d61b..0ffd931c92 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -3,10 +3,14 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.Method import org.opalj.br.analyses.SomeProject +import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeConditionalElement import org.opalj.tac.ArrayLoad +import org.opalj.tac.Assignment +import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StringConst @@ -24,35 +28,38 @@ import org.opalj.tac.VirtualFunctionCall class ExprHandler(p: SomeProject, m: Method) { private val tacProvider = p.get(SimpleTACAIKey) - private val methodStmts = tacProvider(m).stmts + private val ctxStmts = tacProvider(m).stmts /** * Processes a given definition site. That is, this function determines the - * [[StringConstancyProperty]] of a string definition. + * [[StringTree]] of a string definition. * * @param defSite The definition site to process. Make sure that (1) the value is >= 0, (2) it * actually exists, and (3) contains an Assignment whose expression is of a type * that is supported by a sub-class of [[AbstractExprProcessor]]. - * @return Returns an instance of [[StringConstancyProperty]] that describes the definition - * at the specified site. In case the rules listed above or the ones of the different - * processors are not met `None` will be returned. + * @return Returns a StringTee that describes the definition at the specified site. In case the + * rules listed above or the ones of the different processors are not met `None` will be + * returned. */ - def processDefSite(defSite: Int): Option[StringConstancyProperty] = { + def processDefSite(defSite: Int): Option[StringTree] = { if (defSite < 0) { return None } - val expr = methodStmts(defSite).asAssignment.expr - val exprProcessor: AbstractExprProcessor = expr match { + val assignment = ctxStmts(defSite).asAssignment + val exprProcessor: AbstractExprProcessor = assignment.expr match { case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor(this) - case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor() + case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor(this) + case _: New ⇒ new NewStringBuilderProcessor(this) case _: NonVirtualFunctionCall[V] ⇒ new NonVirtualFunctionCallProcessor() case _: StringConst ⇒ new StringConstProcessor() case _ ⇒ throw new IllegalArgumentException( - s"cannot process expression $expr" + s"cannot process expression ${assignment.expr}" ) } - exprProcessor.process(methodStmts, expr) + + val subtree = exprProcessor.process(assignment, ctxStmts) + subtree } /** @@ -62,14 +69,21 @@ class ExprHandler(p: SomeProject, m: Method) { * [[ExprHandler.processDefSite]] apply. * * @param defSites The definition sites to process. - * @return Returns an array of [[StringConstancyProperty]] elements. In contrast to - * [[ExprHandler.processDefSite]] this function returns only those values that are not - * equals `None`. Furthermore, note that this function returns the values unmodified, - * e.g., no call to [[StringConstancyProperty#reduce]] whatsoever is executed that could - * change the array. + * @return Returns a [[StringTree]]. In contrast to [[ExprHandler.processDefSite]] this function + * takes into consideration only those values from `processDefSite` that are not `None`. + * Furthermore, this function assumes that different definition sites originate from + * control flow statements; thus, this function returns a tree with a + * [[TreeConditionalElement]] as root and + * each definition site as a child. */ - def processDefinitionSites(defSites: IntTrieSet): Array[StringConstancyProperty] = - defSites.filter(_ >= 0).map(processDefSite _).filter(_.isDefined).map(_.get).toArray + def processDefSites(defSites: IntTrieSet): Option[StringTree] = + defSites.size match { + case 0 ⇒ None + case 1 ⇒ processDefSite(defSites.head) + case _ ⇒ Some(TreeConditionalElement( + defSites.filter(_ >= 0).map(processDefSite _).filter(_.isDefined).map(_.get).toList + )) + } } @@ -80,4 +94,45 @@ object ExprHandler { */ def apply(p: SomeProject, m: Method): ExprHandler = new ExprHandler(p, m) + /** + * Checks whether an assignment has an expression which is a call to [[StringBuilder.toString]]. + * + * @param a The assignment whose expression is to be checked. + * @return Returns true if `a`'s expression is a call to [[StringBuilder.toString]]. + */ + def isStringBuilderToStringCall(a: Assignment[V]): Boolean = + a.expr match { + case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ + clazz.toJavaClass.getName == "java.lang.StringBuilder" && name == "toString" + case _ ⇒ false + } + + /** + * Checks whether an assignment has an expression which is a call to [[StringBuilder#append]]. + * + * @param a The assignment whose expression is to be checked. + * @return Returns true if `a`'s expression is a call to [[StringBuilder#append]]. + */ + def isStringBuilderAppendCall(a: Assignment[V]): Boolean = + a.expr match { + case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ + clazz.toJavaClass.getName == "java.lang.StringBuilder" && name == "append" + case _ ⇒ false + } + + /** + * Retrieves the definition sites of the receiver of a [[StringBuilder.toString]] call. + * + * @param a The assignment whose expression contains the receiver whose definition sites to get. + * @return If `a` does not conform to the expected structure, an [[EmptyIntTrieSet]] is + * returned (avoid by using [[isStringBuilderToStringCall]]) and otherwise the + * definition sites of the receiver. + */ + def getDefSitesOfToStringReceiver(a: Assignment[V]): IntTrieSet = + if (!isStringBuilderToStringCall(a)) { + EmptyIntTrieSet + } else { + a.expr.asVirtualFunctionCall.receiver.asVar.definedBy + } + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala new file mode 100644 index 0000000000..62e4068ca1 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -0,0 +1,63 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT +import org.opalj.tac.Stmt +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.tac.Assignment +import org.opalj.tac.New +import org.opalj.tac.NonVirtualMethodCall + +import scala.collection.mutable.ListBuffer + +/** + * + * @author Patrick Mell + */ +class NewStringBuilderProcessor( + private val exprHandler: ExprHandler + ) extends AbstractExprProcessor { + + /** + * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will + * be returned). + * + * @see [[AbstractExprProcessor.process()]] + */ + override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = { + assignment.expr match { + case _: New ⇒ + val inits = assignment.targetVar.usedBy.filter { + stmts(_) match { + case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ true + case _ ⇒ false + } + } + val treeNodes = ListBuffer[Option[StringTree]]() + + inits.foreach { next ⇒ + val init = stmts(next).asNonVirtualMethodCall + if (init.params.nonEmpty) { + treeNodes.append( + exprHandler.processDefSites(init.params.head.asVar.definedBy) + ) + } + } + + treeNodes.size match { + case 0 ⇒ + // No argument to constructor was passed => empty string + Some(TreeValueElement(None, StringConstancyInformation(CONSTANT, ""))) + case 1 ⇒ treeNodes.head + case _ ⇒ Some(TreeConditionalElement( + treeNodes.filter(_.isDefined).map(_.get).toList + )) + } + case _ ⇒ None + } + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala index 8adc7ce3ac..1df7cfcac6 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -1,37 +1,39 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing -import org.opalj.tac.Expr + import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.tac.Assignment import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt -import scala.collection.mutable.ArrayBuffer - /** * This implementation of [[AbstractExprProcessor]] processes * [[org.opalj.tac.NonVirtualFunctionCall]] expressions. * Currently, this implementation is only a rough approximation in the sense that all - * `NonVirtualFunctionCall`s are processed by returning - * `StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))`, i.e., do not analyze the function call in - * depth. + * `NonVirtualFunctionCall`s are processed by returning a [[TreeValueElement]] with no children + * and `StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))` as a value (i.e., it does not analyze + * the function call in depth). * * @author Patrick Mell */ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { /** - * `expr` is required to be of type [[org.opalj.tac.NonVirtualFunctionCall]] (otherwise `None` - * will be returned). + * `expr` of `assignment`is required to be of type [[org.opalj.tac.NonVirtualFunctionCall]] + * (otherwise `None` will be returned). * `stmts` currently is not relevant, thus an empty array may be passed. * * @see [[AbstractExprProcessor#process]] */ - override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = - expr match { - case _: NonVirtualFunctionCall[V] ⇒ - Some(StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))) + override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = + assignment.expr match { + case _: NonVirtualFunctionCall[V] ⇒ Some(TreeValueElement( + None, StringConstancyInformation(DYNAMIC, "*") + )) case _ ⇒ None } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala index d7e149364f..1ee2a8211d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyLevel -import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.tac.Expr +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.tac.Assignment import org.opalj.tac.Stmt import org.opalj.tac.StringConst -import scala.collection.mutable.ArrayBuffer - /** * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.StringConst]] * expressions. @@ -18,15 +18,19 @@ import scala.collection.mutable.ArrayBuffer class StringConstProcessor() extends AbstractExprProcessor { /** - * For this implementation, `stmts` is not needed (thus, you may pass an empty Array). `expr` is - * required to be of type [[org.opalj.tac.StringConst]] (otherwise `None` will be returned). + * For this implementation, `stmts` is not required (thus, it is safe to pass an empty value). + * The `expr` of `assignment` is required to be of type [[org.opalj.tac.StringConst]] (otherwise + * `None` will be returned). + * + * @note The sub-tree, which is created by this implementation, does not have any children. * * @see [[AbstractExprProcessor.process]] */ - override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = - expr match { - case strConst: StringConst ⇒ Some(StringConstancyProperty( - StringConstancyLevel.CONSTANT, ArrayBuffer(strConst.value) + override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = + assignment.expr match { + case strConst: StringConst ⇒ Some(TreeValueElement( + None, + StringConstancyInformation(CONSTANT, strConst.value) )) case _ ⇒ None } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index 58fcf2aeb3..2530fde2b9 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -1,18 +1,21 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing -import org.opalj.tac.Expr + import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyLevel.CONSTANT -import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC -import org.opalj.fpcf.properties.StringConstancyLevel.PARTIALLY_CONSTANT -import org.opalj.fpcf.properties.StringConstancyLevel.StringConstancyLevel -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.fpcf.string_definition.properties.TreeElement +import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.tac.Assignment import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.StringConst import org.opalj.tac.VirtualFunctionCall -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.ListBuffer /** * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.VirtualFunctionCall]] @@ -22,47 +25,69 @@ import scala.collection.mutable.ArrayBuffer * * @author Patrick Mell */ -class VirtualFunctionCallProcessor() extends AbstractExprProcessor { +class VirtualFunctionCallProcessor( + private val exprHandler: ExprHandler +) extends AbstractExprProcessor { /** - * `expr` is required to be of type [[org.opalj.tac.VirtualFunctionCall]] (otherwise `None` will - * be returned). + * `expr` of `assignment`is required to be of type [[org.opalj.tac.VirtualFunctionCall]] + * (otherwise `None` will be returned). * * @see [[AbstractExprProcessor.process]] */ - override def process(stmts: Array[Stmt[V]], expr: Expr[V]): Option[StringConstancyProperty] = { - val properties = ArrayBuffer[StringConstancyProperty]() - - expr match { + override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = { + assignment.expr match { case vfc: VirtualFunctionCall[V] ⇒ - // TODO: Are these long concatenations the best / most robust way? - vfc.receiver.asVar.definedBy.foreach { defSite ⇒ - val appendCall = stmts(defSite).asAssignment.expr.asVirtualFunctionCall - appendCall.receiver.asVar.definedBy.foreach { rDefSite ⇒ - var level = CONSTANT - // Get previous value of string builder - val baseAssignment = stmts(rDefSite).asAssignment - val baseStr = valueOfAppendCall( - baseAssignment.expr.asVirtualFunctionCall, stmts - ) - var assignedStr = baseStr._1 - // Get appended value and build the new string value - val appendData = valueOfAppendCall(appendCall, stmts) - if (appendData._2 == CONSTANT) { - assignedStr += appendData._1 - } else { - assignedStr += "*" - level = PARTIALLY_CONSTANT - } - properties.append(StringConstancyProperty(level, ArrayBuffer(assignedStr))) - } + if (ExprHandler.isStringBuilderAppendCall(assignment)) { + Some(processAppendCall(vfc, stmts)) + } else if (ExprHandler.isStringBuilderToStringCall(assignment)) { + Some(processToStringCall(vfc, stmts)) + } // A call to method which is not (yet) supported + else { + None } - StringConstancyProperty.reduce(properties.toArray) - case _ ⇒ None } } + /** + * Function for processing calls to [[StringBuilder#append]]. + */ + private def processAppendCall( + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + ): TreeElement = { + val defSites = call.receiver.asVar.definedBy + val appendValue = valueOfAppendCall(call, stmts) + if (defSites.isEmpty) { + appendValue + } else { + val upperTree = exprHandler.processDefSites(defSites).get + upperTree.getLeafs.foreach { _.child = Some(appendValue) } + upperTree + } + } + + /** + * Function for processing calls to [[StringBuilder.toString]]. + */ + private def processToStringCall( + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + ): StringTree = { + val children = ListBuffer[TreeElement]() + call.receiver.asVar.definedBy.foreach { + exprHandler.processDefSite(_) match { + case Some(subtree) ⇒ children.append(subtree) + case None ⇒ + } + } + + if (children.size == 1) { + children.head + } else { + TreeConditionalElement(children.toList) + } + } + /** * Determines the string value that was passed to a `StringBuilder#append` method. This function * can process string constants as well as function calls as argument to append. @@ -70,25 +95,25 @@ class VirtualFunctionCallProcessor() extends AbstractExprProcessor { * @param call A function call of `StringBuilder#append`. Note that for all other methods an * [[IllegalArgumentException]] will be thrown. * @param stmts The surrounding context, e.g., the surrounding method. - * @return For constants strings as arguments, this function returns the string value and the - * level [[org.opalj.fpcf.properties.StringConstancyLevel.CONSTANT]]. For function calls - * "*" (to indicate ''any value'') and - * [[org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC]]. + * @return Returns a [[TreeValueElement]] with no children and the following value for + * [[StringConstancyInformation]]: For constants strings as arguments, this function + * returns the string value and the level + * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT]]. For + * function calls "*" (to indicate ''any value'') and + * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC]]. */ private def valueOfAppendCall( call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] - ): (String, StringConstancyLevel) = { - // TODO: Check the base object as well - if (call.name != "append") { - throw new IllegalArgumentException("can only process StringBuilder#append calls") - } - + ): TreeValueElement = { val defAssignment = call.params.head.asVar.definedBy.head - val assignExpr = stmts(defAssignment).asAssignment.expr - assignExpr match { - case _: NonVirtualFunctionCall[V] ⇒ Tuple2("*", DYNAMIC) - case StringConst(_, value) ⇒ (value, CONSTANT) + val assign = stmts(defAssignment).asAssignment + val sci = assign.expr match { + case _: NonVirtualFunctionCall[V] ⇒ StringConstancyInformation(DYNAMIC, "*") + case StringConst(_, value) ⇒ StringConstancyInformation(CONSTANT, value) + // Next case is for an append call as argument to append + case _: VirtualFunctionCall[V] ⇒ process(assign, stmts).get.reduce() } + TreeValueElement(None, sci) } -} \ No newline at end of file +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index 9af4ec9ac1..9040df2cd3 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -1,6 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.properties - import org.opalj.br.Field import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS @@ -9,56 +8,25 @@ import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.properties.StringConstancyLevel.CONSTANT -import org.opalj.fpcf.properties.StringConstancyLevel.DYNAMIC -import org.opalj.fpcf.properties.StringConstancyLevel.PARTIALLY_CONSTANT - -import scala.collection.mutable.ArrayBuffer +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.TreeValueElement sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformation { type Self = StringConstancyProperty } -/** - * Values in this enumeration represent the granularity of used strings. - * - * @author Patrick Mell - */ -object StringConstancyLevel extends Enumeration { - - type StringConstancyLevel = StringConstancyLevel.Value - - /** - * This level indicates that a string has a constant value at a given read operation. - */ - final val CONSTANT = Value("constant") - - /** - * This level indicates that a string is partially constant (constant + dynamic part) at some - * read operation, that is, the initial value of a string variable needs to be preserved. For - * instance, it is fine if a string variable is modified after its initialization by - * appending another string, s2. Later, s2 might be removed partially or entirely without - * violating the constraints of this level. - */ - final val PARTIALLY_CONSTANT = Value("partially_constant") - - /** - * This level indicates that a string at some read operations has an unpredictable value. - */ - final val DYNAMIC = Value("dynamic") - -} - class StringConstancyProperty( - val constancyLevel: StringConstancyLevel.Value, - val possibleStrings: ArrayBuffer[String] + val stringTree: StringTree ) extends Property with StringConstancyPropertyMetaInformation { - final def key = StringConstancyProperty.key + final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - val ps = possibleStrings.mkString("[", ", ", "]") - s"StringConstancyProperty { Constancy Level: $constancyLevel; Possible Strings: $ps }" + val sci = stringTree.reduce() + s"StringConstancyProperty { Constancy Level: ${sci.constancyLevel}; "+ + s"Possible Strings: ${sci.possibleStrings} }" } } @@ -70,9 +38,11 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { final val key: PropertyKey[StringConstancyProperty] = { PropertyKey.create( PropertyKeyName, - (_: PropertyStore, _: FallbackReason, e: Entity) ⇒ { + (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - StringConstancyProperty(DYNAMIC, ArrayBuffer("*")) + StringConstancyProperty(TreeValueElement( + None, StringConstancyInformation(DYNAMIC, "*") + )) }, (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, (_: PropertyStore, _: Entity) ⇒ None @@ -80,39 +50,7 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { } def apply( - constancyLevel: StringConstancyLevel.Value, - possibleStrings: ArrayBuffer[String] - ): StringConstancyProperty = new StringConstancyProperty(constancyLevel, possibleStrings) - - /** - * This function takes an array of [[StringConstancyProperty]] and reduces it. This means that - * the most-general [[StringConstancyLevel]] encountered in the given properties is returned - * ([[StringConstancyLevel.CONSTANT]] is the most static > - * [[StringConstancyLevel.PARTIALLY_CONSTANT]] > [[StringConstancyLevel.DYNAMIC]] is the most- - * general) along with the union of all possible strings. Note that this union contains every - * possible string only once (also the "*" marker)! "*" might be contained as well as (semi) - * constant strings to convey all possibilities. - * - * @param properties The properties to reduce. - * @return Returns a single [[StringConstancyProperty]] with values as described above. In case - * the given `properties` array is empty, `None` will be returned. - */ - def reduce(properties: Array[StringConstancyProperty]): Option[StringConstancyProperty] = { - if (properties.isEmpty) { - return None - } - - val possibleValues = ArrayBuffer[String]() - var level = CONSTANT - properties.foreach { next ⇒ - if ((level == CONSTANT) || - (level == PARTIALLY_CONSTANT && next.constancyLevel != CONSTANT)) { - level = next.constancyLevel - } - possibleValues.appendAll(next.possibleStrings) - } - - Some(StringConstancyProperty(level, possibleValues.distinct)) - } + stringTree: StringTree + ): StringConstancyProperty = new StringConstancyProperty(stringTree) } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala new file mode 100644 index 0000000000..4c6598fbbf --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -0,0 +1,16 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.string_definition.properties + +/** + * + * @author Patrick Mell + */ +class StringConstancyInformation( + val constancyLevel: StringConstancyLevel.Value, val possibleStrings: String +) + +object StringConstancyInformation { + def apply( + constancyLevel: StringConstancyLevel.Value, possibleStrings: String + ): StringConstancyInformation = new StringConstancyInformation(constancyLevel, possibleStrings) +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala new file mode 100644 index 0000000000..8bd5203c1d --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala @@ -0,0 +1,53 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.string_definition.properties + +/** + * Values in this enumeration represent the granularity of used strings. + * + * @author Patrick Mell + */ +object StringConstancyLevel extends Enumeration { + + type StringConstancyLevel = StringConstancyLevel.Value + + /** + * This level indicates that a string has a constant value at a given read operation. + */ + final val CONSTANT = Value("constant") + + /** + * This level indicates that a string is partially constant (constant + dynamic part) at some + * read operation, that is, the initial value of a string variable needs to be preserved. For + * instance, it is fine if a string variable is modified after its initialization by + * appending another string, s2. Later, s2 might be removed partially or entirely without + * violating the constraints of this level. + */ + final val PARTIALLY_CONSTANT = Value("partially_constant") + + /** + * This level indicates that a string at some read operations has an unpredictable value. + */ + final val DYNAMIC = Value("dynamic") + + /** + * Returns the more general StringConstancyLevel of the two given levels. DYNAMIC is more + * general than PARTIALLY_CONSTANT which is more general than CONSTANT. + * + * @param level1 The first level. + * @param level2 The second level. + * @return Returns the more general level of both given inputs. + */ + def determineLevel( + level1: StringConstancyLevel, level2: StringConstancyLevel + ): StringConstancyLevel = { + if (level1 == PARTIALLY_CONSTANT || level2 == PARTIALLY_CONSTANT) { + PARTIALLY_CONSTANT + } else if ((level1 == CONSTANT && level2 == DYNAMIC) || + (level1 == DYNAMIC && level2 == CONSTANT)) { + PARTIALLY_CONSTANT + } else { + level1 + } + } + +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala new file mode 100644 index 0000000000..a04a046e91 --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -0,0 +1,135 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.string_definition.properties + +import scala.collection.mutable.ArrayBuffer + +/** + * Models the nodes and leafs of [[StringTree]]. + * TODO: Prepend "String" + * + * @author Patrick Mell + */ +sealed abstract class TreeElement(val children: List[TreeElement]) { + + /** + * Accumulator / helper function for reducing a tree. + * + * @param subtree The tree (or subtree) to reduce. + * @return The reduced tree. + */ + private def reduceAcc( + subtree: TreeElement + ): StringConstancyInformation = { + subtree match { + case TreeConditionalElement(c) ⇒ + val scis = c.map(reduceAcc) + val reducedInfo = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( + StringConstancyLevel.determineLevel(o.constancyLevel, n.constancyLevel), + s"${o.possibleStrings} | ${n.possibleStrings}" + )) + StringConstancyInformation( + reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})" + ) + + case TreeLoopElement(c, nli) ⇒ + val reduced = reduceAcc(c) + val times = if (nli.isDefined) nli.get.toString else "∞" + StringConstancyInformation( + reduced.constancyLevel, + s"(${reduced.possibleStrings})^$times" + ) + + case TreeValueElement(c, sci) ⇒ + c match { + case Some(child) ⇒ + val reduced = reduceAcc(child) + StringConstancyInformation( + StringConstancyLevel.determineLevel( + sci.constancyLevel, reduced.constancyLevel + ), + sci.possibleStrings + reduced.possibleStrings + ) + case None ⇒ sci + } + } + } + + /** + * Reduces this [[StringTree]] instance to a [[StringConstancyInformation]] object that captures + * the information stored in this tree. + * + * @return A [[StringConstancyInformation]] instance that flatly describes this tree. + */ + def reduce(): StringConstancyInformation = reduceAcc(this) + + /** + * @return Returns all leaf elements of this instance. + */ + def getLeafs: Array[TreeValueElement] = { + def leafsAcc(root: TreeElement, leafs: ArrayBuffer[TreeValueElement]): Unit = { + root match { + case TreeLoopElement(c, _) ⇒ leafsAcc(c, leafs) + case TreeConditionalElement(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) + case TreeValueElement(c, _) ⇒ + if (c.isDefined) { + leafsAcc(c.get, leafs) + } else { + leafs.append(root.asInstanceOf[TreeValueElement]) + } + } + } + + val leafs = ArrayBuffer[TreeValueElement]() + leafsAcc(this, leafs) + leafs.toArray + } + +} + +/** + * TreeLoopElement models loops with a [[StringTree]]. `TreeLoopElement`s are supposed to have + * either at lease one other `TreeLoopElement`, `TreeConditionalElement`, or a [[TreeValueElement]] + * as children . A tree with a `TreeLoopElement` that has no children is regarded as an invalid tree + * in this sense!
+ * + * `numLoopIterations` indicates how often the loop iterates. For some loops, this can be statically + * computed - in this case set `numLoopIterations` to that value. When the number of loop iterations + * cannot be determined, set it to [[None]]. + */ +case class TreeLoopElement( + child: TreeElement, + numLoopIterations: Option[Int] +) extends TreeElement(List(child)) + +/** + * For modelling conditionals, such as if, if-else, if-elseif-else, switch, and also as parent + * element for possible array values, but no loops! Even though loops are conditionals as well, + * they are to be modelled using [[TreeLoopElement]] (as they capture further information).
+ * + * `TreeConditionalElement`s are supposed to have either at lease one other + * `TreeConditionalElement`, `TreeLoopElement`, or a [[TreeValueElement]] as children . A tree with + * a `TreeConditionalElement` that has no children is regarded as an invalid tree in this sense! + */ +case class TreeConditionalElement( + override val children: List[TreeElement] +) extends TreeElement(children) + +/** + * TreeExprElement are the only elements which are supposed to act as leafs within a + * [[StringTree]]. + * They may have one `child` but do not need to have children necessarily. Intuitively, a + * TreeExprElement, ''e1'', which has a child ''e2'', represents the concatenation of ''e1'' and + * ''e2''.
+ * + * `sci` is a [[StringConstancyInformation]] instance that resulted from evaluating an + * expression. + */ +case class TreeValueElement( + var child: Option[TreeElement], + sci: StringConstancyInformation +) extends TreeElement( + child match { + case Some(c) ⇒ List(c) + case None ⇒ List() + } +) diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala new file mode 100644 index 0000000000..8816b1f25f --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala @@ -0,0 +1,12 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.string_definition + +package object properties { + + /** + * `StringTree` is used to build trees that represent how a particular string looks and / or how + * it can looks like from a pattern point of view (thus be approximated). + */ + type StringTree = TreeElement + +} From 386fc88d831b84fb357a382b7db501567acfb05b Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:21:49 +0100 Subject: [PATCH 009/316] Improved the analysis in three ways: 1) When constructing StringTrees, seen AST elements are only considered once, 2) StringTrees can be simplified, and 3) their reduction is improved / corrected (now the test method 'multipleDefSites' works) --- .../string_definition/TestMethods.java | 58 +++++++------- .../LocalStringDefinitionMatcher.scala | 2 +- .../AbstractExprProcessor.scala | 4 +- .../expr_processing/ArrayLoadProcessor.scala | 30 ++++--- .../expr_processing/ExprHandler.scala | 14 +++- .../NewStringBuilderProcessor.scala | 22 ++--- .../NonVirtualFunctionCallProcessor.scala | 4 +- .../StringConstProcessor.scala | 4 +- .../VirtualFunctionCallProcessor.scala | 19 +++-- .../StringConstancyInformation.scala | 10 +-- .../properties/StringConstancyLevel.scala | 29 ++++++- .../properties/StringTree.scala | 80 ++++++++++++++++--- .../StringConstancyLevelTests.scala | 43 ++++++++++ 13 files changed, 231 insertions(+), 88 deletions(-) create mode 100644 OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index aeda05f9ca..3d53de8c0e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -131,35 +131,35 @@ public void multipleConstantDefSites(boolean cond) { analyzeString(s); } - // @StringDefinitions( - // value = "a more comprehensive case where multiple definition sites have to be " - // + "considered each with a different string generation mechanism", - // expectedLevel = StringConstancyLevel.DYNAMIC, - // expectedStrings = "(java.lang.Object | * | java.lang.System | java.lang.*)" - // ) - // public void multipleDefSites(int value) { - // String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; - // - // String s; - // switch (value) { - // case 0: - // s = arr[value]; - // break; - // case 1: - // s = arr[value]; - // break; - // case 3: - // s = "java.lang.System"; - // break; - // case 4: - // s = "java.lang." + getSimpleStringBuilderClassName(); - // break; - // default: - // s = getStringBuilderClassName(); - // } - // - // analyzeString(s); - // } + @StringDefinitions( + value = "a more comprehensive case where multiple definition sites have to be " + + "considered each with a different string generation mechanism", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "(java.lang.System | java.lang.* | * | java.lang.Object)" + ) + public void multipleDefSites(int value) { + String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; + + String s; + switch (value) { + case 0: + s = arr[value]; + break; + case 1: + s = arr[value]; + break; + case 3: + s = "java.lang.System"; + break; + case 4: + s = "java.lang." + getSimpleStringBuilderClassName(); + break; + default: + s = getStringBuilderClassName(); + } + + analyzeString(s); + } // @StringDefinitions( // value = "if-else control structure which append to a string builder", diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index ea5b21b333..1efb62f7f7 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -71,7 +71,7 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { val prop = properties.filter( _.isInstanceOf[StringConstancyProperty] ).head.asInstanceOf[StringConstancyProperty] - val reducedProp = prop.stringTree.reduce() + val reducedProp = prop.stringTree.simplify().reduce() val expLevel = getConstancyLevel(a).get val actLevel = reducedProp.constancyLevel.toString diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala index 73c626b3ba..41789fead3 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala @@ -30,6 +30,8 @@ abstract class AbstractExprProcessor() { * cases an exception be thrown). * @see StringConstancyProperty */ - def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] + def process( + assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index 4746633cae..c40e0c37c4 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -31,27 +31,31 @@ class ArrayLoadProcessor( * * @see [[AbstractExprProcessor.process]] */ - override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = { + override def process( + assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = { assignment.expr match { case al: ArrayLoad[V] ⇒ val children = ListBuffer[TreeElement]() // Loop over all possible array values al.arrayRef.asVar.definedBy.foreach { defSite ⇒ - val arrDecl = stmts(defSite) - arrDecl.asAssignment.targetVar.usedBy.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int ⇒ - // Actually, definedBy should contain only one element but for the sake of - // completion, loop over all - // TODO: If not, the tree construction has to be modified - val arrValues = stmts(f).asArrayStore.value.asVar.definedBy.map { - exprHandler.processDefSite _ - }.filter(_.isDefined).map(_.get) - children.appendAll(arrValues) + if (!ignore.contains(defSite)) { + val arrDecl = stmts(defSite) + arrDecl.asAssignment.targetVar.usedBy.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + // Actually, definedBy should contain only one element but for the sake + // of completion, loop over all + // TODO: If not, the tree construction has to be modified + val arrValues = stmts(f).asArrayStore.value.asVar.definedBy.map { + exprHandler.processDefSite _ + }.filter(_.isDefined).map(_.get) + children.appendAll(arrValues) + } } } - Some(TreeConditionalElement(children.toList)) + Some(TreeConditionalElement(children)) case _ ⇒ None } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 0ffd931c92..3bab84cbed 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -16,6 +16,8 @@ import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StringConst import org.opalj.tac.VirtualFunctionCall +import scala.collection.mutable.ListBuffer + /** * `ExprHandler` is responsible for processing expressions that are relevant in order to determine * which value(s) a string read operation might have. These expressions usually come from the @@ -29,6 +31,7 @@ class ExprHandler(p: SomeProject, m: Method) { private val tacProvider = p.get(SimpleTACAIKey) private val ctxStmts = tacProvider(m).stmts + private val processedDefSites = ListBuffer[Int]() /** * Processes a given definition site. That is, this function determines the @@ -42,9 +45,10 @@ class ExprHandler(p: SomeProject, m: Method) { * returned. */ def processDefSite(defSite: Int): Option[StringTree] = { - if (defSite < 0) { + if (defSite < 0 || processedDefSites.contains(defSite)) { return None } + processedDefSites.append(defSite) val assignment = ctxStmts(defSite).asAssignment val exprProcessor: AbstractExprProcessor = assignment.expr match { @@ -80,9 +84,11 @@ class ExprHandler(p: SomeProject, m: Method) { defSites.size match { case 0 ⇒ None case 1 ⇒ processDefSite(defSites.head) - case _ ⇒ Some(TreeConditionalElement( - defSites.filter(_ >= 0).map(processDefSite _).filter(_.isDefined).map(_.get).toList - )) + case _ ⇒ + val processedSites = defSites.filter(_ >= 0).map(processDefSite _) + Some(TreeConditionalElement( + processedSites.filter(_.isDefined).map(_.get).to[ListBuffer] + )) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 62e4068ca1..099da7fca5 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -18,8 +18,8 @@ import scala.collection.mutable.ListBuffer * @author Patrick Mell */ class NewStringBuilderProcessor( - private val exprHandler: ExprHandler - ) extends AbstractExprProcessor { + private val exprHandler: ExprHandler +) extends AbstractExprProcessor { /** * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will @@ -27,7 +27,9 @@ class NewStringBuilderProcessor( * * @see [[AbstractExprProcessor.process()]] */ - override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = { + override def process( + assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = { assignment.expr match { case _: New ⇒ val inits = assignment.targetVar.usedBy.filter { @@ -39,11 +41,13 @@ class NewStringBuilderProcessor( val treeNodes = ListBuffer[Option[StringTree]]() inits.foreach { next ⇒ - val init = stmts(next).asNonVirtualMethodCall - if (init.params.nonEmpty) { - treeNodes.append( - exprHandler.processDefSites(init.params.head.asVar.definedBy) - ) + if (!ignore.contains(next)) { + val init = stmts(next).asNonVirtualMethodCall + if (init.params.nonEmpty) { + treeNodes.append( + exprHandler.processDefSites(init.params.head.asVar.definedBy) + ) + } } } @@ -53,7 +57,7 @@ class NewStringBuilderProcessor( Some(TreeValueElement(None, StringConstancyInformation(CONSTANT, ""))) case 1 ⇒ treeNodes.head case _ ⇒ Some(TreeConditionalElement( - treeNodes.filter(_.isDefined).map(_.get).toList + treeNodes.filter(_.isDefined).map(_.get) )) } case _ ⇒ None diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala index 1df7cfcac6..52bc1a48ca 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -29,7 +29,9 @@ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { * * @see [[AbstractExprProcessor#process]] */ - override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = + override def process( + assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = assignment.expr match { case _: NonVirtualFunctionCall[V] ⇒ Some(TreeValueElement( None, StringConstancyInformation(DYNAMIC, "*") diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala index 1ee2a8211d..5a67bf3440 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala @@ -26,7 +26,9 @@ class StringConstProcessor() extends AbstractExprProcessor { * * @see [[AbstractExprProcessor.process]] */ - override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = + override def process( + assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = assignment.expr match { case strConst: StringConst ⇒ Some(TreeValueElement( None, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index 2530fde2b9..e09f8ea495 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -35,13 +35,15 @@ class VirtualFunctionCallProcessor( * * @see [[AbstractExprProcessor.process]] */ - override def process(assignment: Assignment[V], stmts: Array[Stmt[V]]): Option[StringTree] = { + override def process( + assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = { assignment.expr match { case vfc: VirtualFunctionCall[V] ⇒ if (ExprHandler.isStringBuilderAppendCall(assignment)) { - Some(processAppendCall(vfc, stmts)) + Some(processAppendCall(vfc, stmts, ignore)) } else if (ExprHandler.isStringBuilderToStringCall(assignment)) { - Some(processToStringCall(vfc, stmts)) + Some(processToStringCall(vfc, stmts, ignore)) } // A call to method which is not (yet) supported else { None @@ -54,9 +56,9 @@ class VirtualFunctionCallProcessor( * Function for processing calls to [[StringBuilder#append]]. */ private def processAppendCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] ): TreeElement = { - val defSites = call.receiver.asVar.definedBy + val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) val appendValue = valueOfAppendCall(call, stmts) if (defSites.isEmpty) { appendValue @@ -71,10 +73,11 @@ class VirtualFunctionCallProcessor( * Function for processing calls to [[StringBuilder.toString]]. */ private def processToStringCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] ): StringTree = { val children = ListBuffer[TreeElement]() - call.receiver.asVar.definedBy.foreach { + val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) + defSites.foreach { exprHandler.processDefSite(_) match { case Some(subtree) ⇒ children.append(subtree) case None ⇒ @@ -84,7 +87,7 @@ class VirtualFunctionCallProcessor( if (children.size == 1) { children.head } else { - TreeConditionalElement(children.toList) + TreeConditionalElement(children) } } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 4c6598fbbf..6ee8583423 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -5,12 +5,6 @@ package org.opalj.fpcf.string_definition.properties * * @author Patrick Mell */ -class StringConstancyInformation( - val constancyLevel: StringConstancyLevel.Value, val possibleStrings: String +case class StringConstancyInformation( + constancyLevel: StringConstancyLevel.Value, possibleStrings: String ) - -object StringConstancyInformation { - def apply( - constancyLevel: StringConstancyLevel.Value, possibleStrings: String - ): StringConstancyInformation = new StringConstancyInformation(constancyLevel, possibleStrings) -} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala index 8bd5203c1d..2b9033110b 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala @@ -37,9 +37,32 @@ object StringConstancyLevel extends Enumeration { * @param level2 The second level. * @return Returns the more general level of both given inputs. */ - def determineLevel( - level1: StringConstancyLevel, level2: StringConstancyLevel - ): StringConstancyLevel = { + def determineMoreGeneral( + level1: StringConstancyLevel, level2: StringConstancyLevel + ): StringConstancyLevel = { + if (level1 == DYNAMIC || level2 == DYNAMIC) { + DYNAMIC + } else if (level1 == PARTIALLY_CONSTANT && level2 == PARTIALLY_CONSTANT) { + PARTIALLY_CONSTANT + } else { + CONSTANT + } + } + + /** + * Returns the StringConstancyLevel of a concatenation of two values. + * CONSTANT + CONSTANT = CONSTANT + * DYNAMIC + DYNAMIC = DYNAMIC + * CONSTANT + DYNAMIC = PARTIALLY_CONSTANT + * PARTIALLY_CONSTANT + {DYNAMIC, CONSTANT} = PARTIALLY_CONSTANT + * + * @param level1 The first level. + * @param level2 The second level. + * @return Returns the level for a concatenation. + */ + def determineForConcat( + level1: StringConstancyLevel, level2: StringConstancyLevel + ): StringConstancyLevel = { if (level1 == PARTIALLY_CONSTANT || level2 == PARTIALLY_CONSTANT) { PARTIALLY_CONSTANT } else if ((level1 == CONSTANT && level2 == DYNAMIC) || diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index a04a046e91..0e9c3042d6 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -1,7 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.string_definition.properties +import scala.collection.mutable import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.ListBuffer /** * Models the nodes and leafs of [[StringTree]]. @@ -9,7 +11,7 @@ import scala.collection.mutable.ArrayBuffer * * @author Patrick Mell */ -sealed abstract class TreeElement(val children: List[TreeElement]) { +sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { /** * Accumulator / helper function for reducing a tree. @@ -17,14 +19,12 @@ sealed abstract class TreeElement(val children: List[TreeElement]) { * @param subtree The tree (or subtree) to reduce. * @return The reduced tree. */ - private def reduceAcc( - subtree: TreeElement - ): StringConstancyInformation = { + private def reduceAcc(subtree: TreeElement): StringConstancyInformation = { subtree match { case TreeConditionalElement(c) ⇒ val scis = c.map(reduceAcc) val reducedInfo = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( - StringConstancyLevel.determineLevel(o.constancyLevel, n.constancyLevel), + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), s"${o.possibleStrings} | ${n.possibleStrings}" )) StringConstancyInformation( @@ -44,7 +44,7 @@ sealed abstract class TreeElement(val children: List[TreeElement]) { case Some(child) ⇒ val reduced = reduceAcc(child) StringConstancyInformation( - StringConstancyLevel.determineLevel( + StringConstancyLevel.determineForConcat( sci.constancyLevel, reduced.constancyLevel ), sci.possibleStrings + reduced.possibleStrings @@ -54,6 +54,51 @@ sealed abstract class TreeElement(val children: List[TreeElement]) { } } + /** + * This function removes duplicate [[TreeValueElement]]s from a given list. In this context, two + * elements are equal if their [[TreeValueElement.sci]] information is equal. + * + * @param children The children from which to remove duplicates. + * @return Returns a list of [[TreeElement]] with unique elements. + */ + private def removeDuplicateTreeValues( + children: ListBuffer[TreeElement] + ): ListBuffer[TreeElement] = { + val seen = mutable.Map[StringConstancyInformation, Boolean]() + val unique = ListBuffer[TreeElement]() + children.foreach { + case next @ TreeValueElement(_, sci) ⇒ + if (!seen.contains(sci)) { + seen += (sci → true) + unique.append(next) + } + case loopElement: TreeLoopElement ⇒ unique.append(loopElement) + case condElement: TreeConditionalElement ⇒ unique.append(condElement) + } + unique + } + + /** + * Accumulator function for simplifying a tree. + */ + private def simplifyAcc(subtree: StringTree): StringTree = { + subtree match { + case TreeConditionalElement(cs) ⇒ + cs.foreach { + case nextC @ TreeConditionalElement(subChildren) ⇒ + simplifyAcc(nextC) + subChildren.foreach(subtree.children.append(_)) + subtree.children.-=(nextC) + case _ ⇒ + } + val unique = removeDuplicateTreeValues(cs) + subtree.children.clear() + subtree.children.appendAll(unique) + subtree + case _ ⇒ subtree + } + } + /** * Reduces this [[StringTree]] instance to a [[StringConstancyInformation]] object that captures * the information stored in this tree. @@ -62,6 +107,21 @@ sealed abstract class TreeElement(val children: List[TreeElement]) { */ def reduce(): StringConstancyInformation = reduceAcc(this) + /** + * Simplifies this tree. Currently, this means that when a (sub) tree has a + * [[TreeConditionalElement]] as root, ''r'', and a child, ''c'' (or several children) which is + * a [[TreeConditionalElement]] as well, that ''c'' is attached as a direct child of ''r'' (the + * child [[TreeConditionalElement]] under which ''c'' was located is then removed safely). + * + * @return This function modifies `this` tree and returns this instance, e.g., for chaining + * commands. + * + * @note Applying this function changes the representation of the tree but not produce a + * semantically different tree! Executing this function prior to [[reduce()]] simplifies + * its stringified representation. + */ + def simplify(): StringTree = simplifyAcc(this) + /** * @return Returns all leaf elements of this instance. */ @@ -99,7 +159,7 @@ sealed abstract class TreeElement(val children: List[TreeElement]) { case class TreeLoopElement( child: TreeElement, numLoopIterations: Option[Int] -) extends TreeElement(List(child)) +) extends TreeElement(ListBuffer(child)) /** * For modelling conditionals, such as if, if-else, if-elseif-else, switch, and also as parent @@ -111,7 +171,7 @@ case class TreeLoopElement( * a `TreeConditionalElement` that has no children is regarded as an invalid tree in this sense! */ case class TreeConditionalElement( - override val children: List[TreeElement] + override val children: ListBuffer[TreeElement] ) extends TreeElement(children) /** @@ -129,7 +189,7 @@ case class TreeValueElement( sci: StringConstancyInformation ) extends TreeElement( child match { - case Some(c) ⇒ List(c) - case None ⇒ List() + case Some(c) ⇒ ListBuffer(c) + case None ⇒ ListBuffer() } ) diff --git a/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala b/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala new file mode 100644 index 0000000000..b9268749a5 --- /dev/null +++ b/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.br.string_definition + +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.PARTIALLY_CONSTANT +import org.scalatest.FunSuite + +/** + * Tests for [[StringConstancyLevel]] methods. + * + * @author Patrick Mell + */ +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class StringConstancyLevelTests extends FunSuite { + + test("tests that the more general string constancy level is computed correctly") { + // Trivial cases + assert(StringConstancyLevel.determineMoreGeneral(DYNAMIC, DYNAMIC) == DYNAMIC) + assert(StringConstancyLevel.determineMoreGeneral( + PARTIALLY_CONSTANT, PARTIALLY_CONSTANT + ) == PARTIALLY_CONSTANT) + assert(StringConstancyLevel.determineMoreGeneral(CONSTANT, CONSTANT) == CONSTANT) + + // Test all other cases, start with { DYNAMIC, CONSTANT } + assert(StringConstancyLevel.determineMoreGeneral(CONSTANT, DYNAMIC) == DYNAMIC) + assert(StringConstancyLevel.determineMoreGeneral(DYNAMIC, CONSTANT) == DYNAMIC) + + // { DYNAMIC, PARTIALLY_CONSTANT } + assert(StringConstancyLevel.determineMoreGeneral(PARTIALLY_CONSTANT, DYNAMIC) == DYNAMIC) + assert(StringConstancyLevel.determineMoreGeneral(DYNAMIC, PARTIALLY_CONSTANT) == DYNAMIC) + + // { PARTIALLY_CONSTANT, CONSTANT } + assert(StringConstancyLevel.determineMoreGeneral( + PARTIALLY_CONSTANT, CONSTANT + ) == PARTIALLY_CONSTANT) + assert(StringConstancyLevel.determineMoreGeneral( + CONSTANT, PARTIALLY_CONSTANT + ) == PARTIALLY_CONSTANT) + } + +} From b03bf5d04e021c7343da6fae9ce3b2b6d383a8dd Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:24:02 +0100 Subject: [PATCH 010/316] Some if-else test cases work and integer values are recognized as well. --- .../string_definition/TestMethods.java | 78 +++++++++++++++---- .../LocalStringDefinitionAnalysis.scala | 9 ++- .../AbstractExprProcessor.scala | 33 +++++++- .../expr_processing/ArrayLoadProcessor.scala | 24 +++++- .../expr_processing/ExprHandler.scala | 68 +++++++++++----- .../NewStringBuilderProcessor.scala | 65 ++++++++++++---- .../NonVirtualFunctionCallProcessor.scala | 27 ++++++- .../StringConstProcessor.scala | 30 +++++-- .../VirtualFunctionCallProcessor.scala | 39 ++++++++-- 9 files changed, 298 insertions(+), 75 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 3d53de8c0e..b7dea5accc 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -4,6 +4,8 @@ import org.opalj.fpcf.properties.string_definition.StringConstancyLevel; import org.opalj.fpcf.properties.string_definition.StringDefinitions; +import java.util.Random; + /** * @author Patrick Mell */ @@ -161,21 +163,69 @@ public void multipleDefSites(int value) { analyzeString(s); } - // @StringDefinitions( - // value = "if-else control structure which append to a string builder", - // expectedLevel = StringConstancyLevel.DYNAMIC, - // expectedStrings = "x | [Int Value]" - // ) - // public void ifElseWithStringBuilder() { - // StringBuilder sb = new StringBuilder(); - // int i = new Random().nextInt(); - // if (i % 2 == 0) { - // sb.append("x"); - // } else { - // sb.append(i + 1); - // } - // analyzeString(sb.toString()); + @StringDefinitions( + value = "if-else control structure which append to a string builder with an int expr", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "(x | [AnIntegerValue])" + ) + public void ifElseWithStringBuilderWithIntExpr() { + StringBuilder sb = new StringBuilder(); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("x"); + } else { + sb.append(i + 1); + } + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "if-else control structure which append to a string builder with an int", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "(x | [AnIntegerValue])" + ) + public void ifElseWithStringBuilderWithConstantInt() { + StringBuilder sb = new StringBuilder(); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("x"); + } else { + sb.append(i); + } + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "if-else control structure which append to a string builder", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "(b | a)" + ) + public void ifElseWithStringBuilder1() { + StringBuilder sb; + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb = new StringBuilder("a"); + } else { + sb = new StringBuilder("b"); + } + analyzeString(sb.toString()); + } + + // @StringDefinitions( + // value = "if-else control structure which append to a string builder", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "(ac | ab)" + // ) + // public void ifElseWithStringBuilder2() { + // StringBuilder sb = new StringBuilder("a"); + // int i = new Random().nextInt(); + // if (i % 2 == 0) { + // sb.append("b"); + // } else { + // sb.append("c"); // } + // analyzeString(sb.toString()); + // } // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 075ad6dfb5..1358146079 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -17,6 +17,7 @@ import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.ListBuffer class StringTrackingAnalysisContext( val stmts: Array[Stmt[V]] @@ -46,21 +47,21 @@ class LocalStringDefinitionAnalysis( val exprHandler = ExprHandler(p, data._2) val defSites = data._1.definedBy - if (ExprHandler.isStringBuilderToStringCall(stmts(defSites.head).asAssignment)) { + if (ExprHandler.isStringBuilderToStringCall(stmts(defSites.head).asAssignment.expr)) { val subtrees = ArrayBuffer[StringTree]() defSites.foreach { nextDefSite ⇒ val treeElements = ExprHandler.getDefSitesOfToStringReceiver( - stmts(nextDefSite).asAssignment + stmts(nextDefSite).asAssignment.expr ).map { exprHandler.processDefSite _ }.filter(_.isDefined).map { _.get } if (treeElements.size == 1) { subtrees.append(treeElements.head) } else { - subtrees.append(TreeConditionalElement(treeElements.toList)) + subtrees.append(TreeConditionalElement(treeElements.to[ListBuffer])) } } val finalTree = if (subtrees.size == 1) subtrees.head else - TreeConditionalElement(subtrees.toList) + TreeConditionalElement(subtrees.to[ListBuffer]) Result(data, StringConstancyProperty(finalTree)) } // If not a call to StringBuilder.toString, then we deal with pure strings else { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala index 41789fead3..f6a4106904 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala @@ -4,6 +4,7 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.tac.Assignment +import org.opalj.tac.Expr import org.opalj.tac.Stmt /** @@ -17,21 +18,45 @@ import org.opalj.tac.Stmt abstract class AbstractExprProcessor() { /** - * Implementations process an expression which is supposed to yield (not necessarily fixed) a - * string value. + * Implementations process an assignment which is supposed to yield a string tree. * * @param assignment The Assignment to process. Make sure that the assignment, which is * passed, meets the requirements of that implementation. * @param stmts The statements that surround the expression to process, such as a method. * Concrete processors might use these to retrieve further information. - * @return Determines the [[StringTree]] for the given `expr` and `stmts` from which as possible + * @param ignore A list of processed def or use sites. This list makes sure that an assignment + * or expression is not processed twice (which could lead to duplicate + * computations and unnecessary elements in the resulting string tree. + * @return Determines the [[StringTree]] for the given `expr` and `stmts` from which possible * string values, which the expression might produce, can be derived. If `expr` does not * meet the requirements of a an implementation, `None` will be returned (or in severe * cases an exception be thrown). * @see StringConstancyProperty */ - def process( + def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() ): Option[StringTree] + /** + * Implementations process an expression which is supposed to yield a string tree. + * + * @param expr The [[Expr]] to process. Make sure that the expression, which is passed, meets + * the requirements of the corresponding implementation. + * @param stmts The statements that surround the expression to process, such as a method. + * Concrete processors might use these to retrieve further information. + * @param ignore A list of processed def or use sites. This list makes sure that an assignment + * or expression is not processed twice (which could lead to duplicate + * computations and unnecessary elements in the resulting string tree. + * @return Determines the [[StringTree]] for the given `expr` from which possible string values, + * which the expression might produce, can be derived. If `expr` does not + * meet the requirements of a an implementation, `None` will be returned (or in severe + * cases an exception be thrown). + * + * @note Note that implementations of [[AbstractExprProcessor]] are not required to implement + * this method (by default, `None` will be returned. + */ + def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = { None } + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index c40e0c37c4..f62e234334 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -7,6 +7,7 @@ import org.opalj.fpcf.string_definition.properties.TreeElement import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore import org.opalj.tac.Assignment +import org.opalj.tac.Expr import org.opalj.tac.Stmt import scala.collection.mutable.ListBuffer @@ -29,12 +30,29 @@ class ArrayLoadProcessor( * The `expr` of `assignment`is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise * `None` will be returned). * - * @see [[AbstractExprProcessor.process]] + * @see [[AbstractExprProcessor.processAssignment]] */ - override def process( + override def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = process(assignment.expr, stmts, ignore) + + /** + * The `expr` of `assignment`is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise + * * `None` will be returned). + * * + * * @see [[AbstractExprProcessor.processExpr]] + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTree] = process(expr, stmts, ignore) + + /** + * Wrapper function for processing an expression. + */ + private def process( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { - assignment.expr match { + expr match { case al: ArrayLoad[V] ⇒ val children = ListBuffer[TreeElement]() // Loop over all possible array values diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 3bab84cbed..48607da72e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -10,6 +10,8 @@ import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeConditionalElement import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment +import org.opalj.tac.Expr +import org.opalj.tac.ExprStmt import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.SimpleTACAIKey @@ -50,19 +52,31 @@ class ExprHandler(p: SomeProject, m: Method) { } processedDefSites.append(defSite) - val assignment = ctxStmts(defSite).asAssignment - val exprProcessor: AbstractExprProcessor = assignment.expr match { + // Determine whether to process an assignment or an expression + val expr = ctxStmts(defSite) match { + case a: Assignment[V] ⇒ a.expr + case e: ExprStmt[V] ⇒ e.expr + case _ ⇒ throw new IllegalArgumentException( + s"cannot process ${ctxStmts(defSite)}" + ) + } + val exprProcessor: AbstractExprProcessor = expr match { case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor(this) case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor(this) case _: New ⇒ new NewStringBuilderProcessor(this) case _: NonVirtualFunctionCall[V] ⇒ new NonVirtualFunctionCallProcessor() case _: StringConst ⇒ new StringConstProcessor() case _ ⇒ throw new IllegalArgumentException( - s"cannot process expression ${assignment.expr}" + s"cannot process expression $expr" ) } - val subtree = exprProcessor.process(assignment, ctxStmts) + val subtree = ctxStmts(defSite) match { + case a: Assignment[V] ⇒ + exprProcessor.processAssignment(a, ctxStmts, processedDefSites.toList) + case _ ⇒ + exprProcessor.processExpr(expr, ctxStmts, processedDefSites.toList) + } subtree } @@ -95,32 +109,37 @@ class ExprHandler(p: SomeProject, m: Method) { object ExprHandler { + private val classNameMap = Map( + "AnIntegerValue" → "[AnIntegerValue]", + "int" → "[AnIntegerValue]" + ) + /** * @see [[ExprHandler]] */ def apply(p: SomeProject, m: Method): ExprHandler = new ExprHandler(p, m) /** - * Checks whether an assignment has an expression which is a call to [[StringBuilder.toString]]. + * Checks whether an expression contains a call to [[StringBuilder.toString]]. * - * @param a The assignment whose expression is to be checked. - * @return Returns true if `a`'s expression is a call to [[StringBuilder.toString]]. + * @param expr The expression that is to be checked. + * @return Returns true if `expr` is a call to [[StringBuilder.toString]]. */ - def isStringBuilderToStringCall(a: Assignment[V]): Boolean = - a.expr match { + def isStringBuilderToStringCall(expr: Expr[V]): Boolean = + expr match { case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ clazz.toJavaClass.getName == "java.lang.StringBuilder" && name == "toString" case _ ⇒ false } /** - * Checks whether an assignment has an expression which is a call to [[StringBuilder#append]]. + * Checks whether an expression is a call to [[StringBuilder#append]]. * - * @param a The assignment whose expression is to be checked. - * @return Returns true if `a`'s expression is a call to [[StringBuilder#append]]. + * @param expr The expression that is to be checked. + * @return Returns true if `expr` is a call to [[StringBuilder#append]]. */ - def isStringBuilderAppendCall(a: Assignment[V]): Boolean = - a.expr match { + def isStringBuilderAppendCall(expr: Expr[V]): Boolean = + expr match { case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ clazz.toJavaClass.getName == "java.lang.StringBuilder" && name == "append" case _ ⇒ false @@ -129,16 +148,27 @@ object ExprHandler { /** * Retrieves the definition sites of the receiver of a [[StringBuilder.toString]] call. * - * @param a The assignment whose expression contains the receiver whose definition sites to get. - * @return If `a` does not conform to the expected structure, an [[EmptyIntTrieSet]] is + * @param expr The expression that contains the receiver whose definition sites to get. + * @return If `expr` does not conform to the expected structure, an [[EmptyIntTrieSet]] is * returned (avoid by using [[isStringBuilderToStringCall]]) and otherwise the * definition sites of the receiver. */ - def getDefSitesOfToStringReceiver(a: Assignment[V]): IntTrieSet = - if (!isStringBuilderToStringCall(a)) { + def getDefSitesOfToStringReceiver(expr: Expr[V]): IntTrieSet = + if (!isStringBuilderToStringCall(expr)) { EmptyIntTrieSet } else { - a.expr.asVirtualFunctionCall.receiver.asVar.definedBy + expr.asVirtualFunctionCall.receiver.asVar.definedBy } + /** + * Maps a class name to a string which is to be displayed as a possible string. + * + * @param javaSimpleClassName The simple class name, i.e., NOT fully-qualified, for which to + * retrieve the value for "possible string". + * @return Either returns the mapped string representation or, when an unknown string is passed, + * the passed parameter surrounded by "[" and "]". + */ + def classNameToPossibleString(javaSimpleClassName: String): String = + classNameMap.getOrElse(javaSimpleClassName, s"[$javaSimpleClassName]") + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 099da7fca5..d33e0795b7 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -1,5 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing + +import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT @@ -8,6 +10,7 @@ import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeConditionalElement import org.opalj.fpcf.string_definition.properties.TreeValueElement import org.opalj.tac.Assignment +import org.opalj.tac.Expr import org.opalj.tac.New import org.opalj.tac.NonVirtualMethodCall @@ -25,29 +28,30 @@ class NewStringBuilderProcessor( * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will * be returned). * - * @see [[AbstractExprProcessor.process()]] + * @see [[AbstractExprProcessor.processAssignment()]] */ - override def process( + override def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() ): Option[StringTree] = { assignment.expr match { case _: New ⇒ - val inits = assignment.targetVar.usedBy.filter { - stmts(_) match { - case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ true - case _ ⇒ false - } - } + val useSites = assignment.targetVar.usedBy.filter(!ignore.contains(_)) + val (inits, nonInits) = getInitsAndNonInits(useSites, stmts) val treeNodes = ListBuffer[Option[StringTree]]() inits.foreach { next ⇒ - if (!ignore.contains(next)) { - val init = stmts(next).asNonVirtualMethodCall - if (init.params.nonEmpty) { - treeNodes.append( - exprHandler.processDefSites(init.params.head.asVar.definedBy) - ) - } + val init = stmts(next).asNonVirtualMethodCall + if (init.params.nonEmpty) { + treeNodes.append( + exprHandler.processDefSites(init.params.head.asVar.definedBy) + ) + } + } + + nonInits.foreach { next ⇒ + val tree = exprHandler.processDefSite(next) + if (tree.isDefined) { + treeNodes.append(tree) } } @@ -64,4 +68,35 @@ class NewStringBuilderProcessor( } } + /** + * This implementation does not change / implement the behavior of + * [[AbstractExprProcessor.processExpr]]. + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTree] = super.processExpr(expr, stmts, ignore) + + /** + * + * @param useSites Not-supposed to contain already processed sites. + * @param stmts A list of statements (the one that was passed on to the `process`function of + * this class). + * @return + */ + private def getInitsAndNonInits( + useSites: IntTrieSet, stmts: Array[Stmt[V]] + ): (List[Int], List[Int]) = { + val inits = ListBuffer[Int]() + val nonInits = ListBuffer[Int]() + useSites.foreach { next ⇒ + stmts(next) match { + case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ + inits.append(next) + case _ ⇒ + nonInits.append(next) + } + } + (inits.toList, nonInits.toList) + } + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala index 52bc1a48ca..a87a89e4a2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -7,6 +7,7 @@ import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeValueElement import org.opalj.tac.Assignment +import org.opalj.tac.Expr import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt @@ -27,16 +28,34 @@ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { * (otherwise `None` will be returned). * `stmts` currently is not relevant, thus an empty array may be passed. * - * @see [[AbstractExprProcessor#process]] + * @see [[AbstractExprProcessor.processAssignment]] */ - override def process( + override def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() - ): Option[StringTree] = - assignment.expr match { + ): Option[StringTree] = process(assignment.expr, stmts, ignore) + + /** + * `expr` is required to be of type [[org.opalj.tac.NonVirtualFunctionCall]] (otherwise `None` + * will be returned). `stmts` currently is not relevant, thus an empty array may be passed. + * + * @see [[AbstractExprProcessor.processExpr()]] + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = process(expr, stmts, ignore) + + /** + * Wrapper function for processing. + */ + private def process( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTree] = { + expr match { case _: NonVirtualFunctionCall[V] ⇒ Some(TreeValueElement( None, StringConstancyInformation(DYNAMIC, "*") )) case _ ⇒ None } + } } \ No newline at end of file diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala index 5a67bf3440..0e33c1c40b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala @@ -6,6 +6,7 @@ import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeValueElement import org.opalj.tac.Assignment +import org.opalj.tac.Expr import org.opalj.tac.Stmt import org.opalj.tac.StringConst @@ -23,18 +24,37 @@ class StringConstProcessor() extends AbstractExprProcessor { * `None` will be returned). * * @note The sub-tree, which is created by this implementation, does not have any children. - * - * @see [[AbstractExprProcessor.process]] + * @see [[AbstractExprProcessor.processAssignment]] */ - override def process( + override def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() - ): Option[StringTree] = - assignment.expr match { + ): Option[StringTree] = process(assignment.expr, stmts, ignore) + + /** + * For this implementation, `stmts` is not required (thus, it is safe to pass an empty value). + * `expr` is required to be of type [[org.opalj.tac.StringConst]] (otherwise `None` will be + * returned). + * + * @note The sub-tree, which is created by this implementation, does not have any children. + * @see [[AbstractExprProcessor.processExpr()]] + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = process(expr, stmts, ignore) + + /** + * Wrapper function for processing an expression. + */ + private def process( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTree] = { + expr match { case strConst: StringConst ⇒ Some(TreeValueElement( None, StringConstancyInformation(CONSTANT, strConst.value) )) case _ ⇒ None } + } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index e09f8ea495..ef68682af1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -10,6 +10,8 @@ import org.opalj.fpcf.string_definition.properties.TreeConditionalElement import org.opalj.fpcf.string_definition.properties.TreeElement import org.opalj.fpcf.string_definition.properties.TreeValueElement import org.opalj.tac.Assignment +import org.opalj.tac.BinaryExpr +import org.opalj.tac.Expr import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.StringConst @@ -33,20 +35,38 @@ class VirtualFunctionCallProcessor( * `expr` of `assignment`is required to be of type [[org.opalj.tac.VirtualFunctionCall]] * (otherwise `None` will be returned). * - * @see [[AbstractExprProcessor.process]] + * @see [[AbstractExprProcessor.processAssignment]] */ - override def process( + override def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + ): Option[StringTree] = process(assignment.expr, stmts, ignore) + + /** + * This implementation does not change / implement the behavior of + * [[AbstractExprProcessor.processExpr]]. + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTree] = process(expr, stmts, ignore) + + /** + * Wrapper function for processing expressions. + */ + private def process( + expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { - assignment.expr match { + expr match { case vfc: VirtualFunctionCall[V] ⇒ - if (ExprHandler.isStringBuilderAppendCall(assignment)) { + if (ExprHandler.isStringBuilderAppendCall(expr)) { Some(processAppendCall(vfc, stmts, ignore)) - } else if (ExprHandler.isStringBuilderToStringCall(assignment)) { + } else if (ExprHandler.isStringBuilderToStringCall(expr)) { Some(processToStringCall(vfc, stmts, ignore)) } // A call to method which is not (yet) supported else { - None + val ps = ExprHandler.classNameToPossibleString( + vfc.descriptor.returnType.toJavaClass.getSimpleName + ) + Some(TreeValueElement(None, StringConstancyInformation(DYNAMIC, ps))) } case _ ⇒ None } @@ -114,7 +134,12 @@ class VirtualFunctionCallProcessor( case _: NonVirtualFunctionCall[V] ⇒ StringConstancyInformation(DYNAMIC, "*") case StringConst(_, value) ⇒ StringConstancyInformation(CONSTANT, value) // Next case is for an append call as argument to append - case _: VirtualFunctionCall[V] ⇒ process(assign, stmts).get.reduce() + case _: VirtualFunctionCall[V] ⇒ processAssignment(assign, stmts).get.reduce() + case be: BinaryExpr[V] ⇒ + val possibleString = ExprHandler.classNameToPossibleString( + be.left.asVar.value.getClass.getSimpleName + ) + StringConstancyInformation(DYNAMIC, possibleString) } TreeValueElement(None, sci) } From f37a7129328af469372a4b72cb6b717eb3d8d983 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:26:38 +0100 Subject: [PATCH 011/316] Improved the construction of string trees with regard to if-else blocks. --- .../string_definition/TestMethods.java | 30 ++++---- .../AbstractExprProcessor.scala | 9 ++- .../expr_processing/ArrayLoadProcessor.scala | 8 +- .../expr_processing/ExprHandler.scala | 7 +- .../NewStringBuilderProcessor.scala | 73 +++++++++++++------ .../NonVirtualFunctionCallProcessor.scala | 8 +- .../StringConstProcessor.scala | 8 +- .../VirtualFunctionCallProcessor.scala | 25 ++++--- .../properties/StringTree.scala | 29 +++++--- 9 files changed, 126 insertions(+), 71 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index b7dea5accc..92d0afaa50 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -211,21 +211,21 @@ public void ifElseWithStringBuilder1() { analyzeString(sb.toString()); } - // @StringDefinitions( - // value = "if-else control structure which append to a string builder", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "(ac | ab)" - // ) - // public void ifElseWithStringBuilder2() { - // StringBuilder sb = new StringBuilder("a"); - // int i = new Random().nextInt(); - // if (i % 2 == 0) { - // sb.append("b"); - // } else { - // sb.append("c"); - // } - // analyzeString(sb.toString()); - // } + @StringDefinitions( + value = "if-else control structure which append to a string builder", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b | c)" + ) + public void ifElseWithStringBuilder2() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("b"); + } else { + sb.append("c"); + } + analyzeString(sb.toString()); + } // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala index f6a4106904..da5110f81e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala @@ -1,11 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts /** * AbstractExprProcessor defines the abstract / general strategy to process expressions in the @@ -24,6 +26,7 @@ abstract class AbstractExprProcessor() { * passed, meets the requirements of that implementation. * @param stmts The statements that surround the expression to process, such as a method. * Concrete processors might use these to retrieve further information. + * @param cfg The control flow graph that corresponds to the given `stmts` * @param ignore A list of processed def or use sites. This list makes sure that an assignment * or expression is not processed twice (which could lead to duplicate * computations and unnecessary elements in the resulting string tree. @@ -34,7 +37,8 @@ abstract class AbstractExprProcessor() { * @see StringConstancyProperty */ def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] /** @@ -56,7 +60,8 @@ abstract class AbstractExprProcessor() { * this method (by default, `None` will be returned. */ def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = { None } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index f62e234334..95365f26c8 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -1,5 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeConditionalElement @@ -9,6 +10,7 @@ import org.opalj.tac.ArrayStore import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts import scala.collection.mutable.ListBuffer @@ -33,7 +35,8 @@ class ArrayLoadProcessor( * @see [[AbstractExprProcessor.processAssignment]] */ override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(assignment.expr, stmts, ignore) /** @@ -43,7 +46,8 @@ class ArrayLoadProcessor( * * @see [[AbstractExprProcessor.processExpr]] */ override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(expr, stmts, ignore) /** diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 48607da72e..e2ba0154b8 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -34,6 +34,7 @@ class ExprHandler(p: SomeProject, m: Method) { private val tacProvider = p.get(SimpleTACAIKey) private val ctxStmts = tacProvider(m).stmts private val processedDefSites = ListBuffer[Int]() + private val cfg = tacProvider(m).cfg /** * Processes a given definition site. That is, this function determines the @@ -62,7 +63,7 @@ class ExprHandler(p: SomeProject, m: Method) { } val exprProcessor: AbstractExprProcessor = expr match { case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor(this) - case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor(this) + case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor(this, cfg) case _: New ⇒ new NewStringBuilderProcessor(this) case _: NonVirtualFunctionCall[V] ⇒ new NonVirtualFunctionCallProcessor() case _: StringConst ⇒ new StringConstProcessor() @@ -73,9 +74,9 @@ class ExprHandler(p: SomeProject, m: Method) { val subtree = ctxStmts(defSite) match { case a: Assignment[V] ⇒ - exprProcessor.processAssignment(a, ctxStmts, processedDefSites.toList) + exprProcessor.processAssignment(a, ctxStmts, cfg, processedDefSites.toList) case _ ⇒ - exprProcessor.processExpr(expr, ctxStmts, processedDefSites.toList) + exprProcessor.processExpr(expr, ctxStmts, cfg, processedDefSites.toList) } subtree } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index d33e0795b7..a2e200ca34 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.CFG +import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation @@ -13,6 +15,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.New import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.TACStmts import scala.collection.mutable.ListBuffer @@ -28,41 +31,63 @@ class NewStringBuilderProcessor( * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will * be returned). * - * @see [[AbstractExprProcessor.processAssignment()]] + * @see [[AbstractExprProcessor.processAssignment]] */ override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = { assignment.expr match { case _: New ⇒ val useSites = assignment.targetVar.usedBy.filter(!ignore.contains(_)) val (inits, nonInits) = getInitsAndNonInits(useSites, stmts) - val treeNodes = ListBuffer[Option[StringTree]]() + val initTreeNodes = ListBuffer[StringTree]() + val nonInitTreeNodes = ListBuffer[StringTree]() inits.foreach { next ⇒ - val init = stmts(next).asNonVirtualMethodCall - if (init.params.nonEmpty) { - treeNodes.append( - exprHandler.processDefSites(init.params.head.asVar.definedBy) - ) + val toProcess = stmts(next) match { + case init: NonVirtualMethodCall[V] if init.params.nonEmpty ⇒ + init.params.head.asVar.definedBy + case assignment: Assignment[V] ⇒ + assignment.expr.asVirtualFunctionCall.receiver.asVar.definedBy + case _ ⇒ + EmptyIntTrieSet } + exprHandler.processDefSites(toProcess) match { + case Some(toAppend) ⇒ initTreeNodes.append(toAppend) + case None ⇒ + } + } + // No argument to constructor was passed => empty string with nonInits as child + if (initTreeNodes.isEmpty) { + initTreeNodes.append(TreeValueElement( + None, StringConstancyInformation(CONSTANT, "") + )) } nonInits.foreach { next ⇒ val tree = exprHandler.processDefSite(next) if (tree.isDefined) { - treeNodes.append(tree) + nonInitTreeNodes.append(tree.get) } } - treeNodes.size match { - case 0 ⇒ - // No argument to constructor was passed => empty string - Some(TreeValueElement(None, StringConstancyInformation(CONSTANT, ""))) - case 1 ⇒ treeNodes.head - case _ ⇒ Some(TreeConditionalElement( - treeNodes.filter(_.isDefined).map(_.get) - )) + if (nonInitTreeNodes.nonEmpty) { + initTreeNodes.foreach { next ⇒ + val toAppend = nonInitTreeNodes.size match { + case 1 ⇒ nonInitTreeNodes.head + case _ ⇒ TreeConditionalElement(nonInitTreeNodes) + } + next match { + case tve: TreeValueElement ⇒ tve.child = Some(toAppend) + case _ ⇒ next.children.append(toAppend) + } + } + } + + initTreeNodes.size match { + case 1 ⇒ Some(initTreeNodes.head) + case _ ⇒ Some(TreeConditionalElement(initTreeNodes)) } case _ ⇒ None } @@ -73,8 +98,9 @@ class NewStringBuilderProcessor( * [[AbstractExprProcessor.processExpr]]. */ override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = super.processExpr(expr, stmts, ignore) + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() + ): Option[StringTree] = super.processExpr(expr, stmts, cfg, ignore) /** * @@ -90,10 +116,11 @@ class NewStringBuilderProcessor( val nonInits = ListBuffer[Int]() useSites.foreach { next ⇒ stmts(next) match { - case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ - inits.append(next) - case _ ⇒ - nonInits.append(next) + // Constructors are identified by the "init" method and assignments (ExprStmts, in + // contrast, point to non-constructor related calls) + case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ inits.append(next) + case _: Assignment[V] ⇒ inits.append(next) + case _ ⇒ nonInits.append(next) } } (inits.toList, nonInits.toList) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala index a87a89e4a2..8ee4cf4ed8 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC @@ -10,6 +11,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts /** * This implementation of [[AbstractExprProcessor]] processes @@ -31,7 +33,8 @@ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { * @see [[AbstractExprProcessor.processAssignment]] */ override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(assignment.expr, stmts, ignore) /** @@ -41,7 +44,8 @@ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { * @see [[AbstractExprProcessor.processExpr()]] */ override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(expr, stmts, ignore) /** diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala index 0e33c1c40b..2418411a16 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala @@ -1,5 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT @@ -9,6 +10,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.Stmt import org.opalj.tac.StringConst +import org.opalj.tac.TACStmts /** * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.StringConst]] @@ -27,7 +29,8 @@ class StringConstProcessor() extends AbstractExprProcessor { * @see [[AbstractExprProcessor.processAssignment]] */ override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(assignment.expr, stmts, ignore) /** @@ -39,7 +42,8 @@ class StringConstProcessor() extends AbstractExprProcessor { * @see [[AbstractExprProcessor.processExpr()]] */ override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(expr, stmts, ignore) /** diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index ef68682af1..e94974384e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT @@ -15,6 +16,7 @@ import org.opalj.tac.Expr import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.StringConst +import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import scala.collection.mutable.ListBuffer @@ -28,7 +30,8 @@ import scala.collection.mutable.ListBuffer * @author Patrick Mell */ class VirtualFunctionCallProcessor( - private val exprHandler: ExprHandler + private val exprHandler: ExprHandler, + private val cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractExprProcessor { /** @@ -38,7 +41,8 @@ class VirtualFunctionCallProcessor( * @see [[AbstractExprProcessor.processAssignment]] */ override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], ignore: List[Int] = List[Int]() + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(assignment.expr, stmts, ignore) /** @@ -46,7 +50,8 @@ class VirtualFunctionCallProcessor( * [[AbstractExprProcessor.processExpr]]. */ override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() ): Option[StringTree] = process(expr, stmts, ignore) /** @@ -60,7 +65,7 @@ class VirtualFunctionCallProcessor( if (ExprHandler.isStringBuilderAppendCall(expr)) { Some(processAppendCall(vfc, stmts, ignore)) } else if (ExprHandler.isStringBuilderToStringCall(expr)) { - Some(processToStringCall(vfc, stmts, ignore)) + processToStringCall(vfc, stmts, ignore) } // A call to method which is not (yet) supported else { val ps = ExprHandler.classNameToPossibleString( @@ -94,7 +99,7 @@ class VirtualFunctionCallProcessor( */ private def processToStringCall( call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): StringTree = { + ): Option[StringTree] = { val children = ListBuffer[TreeElement]() val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) defSites.foreach { @@ -104,10 +109,10 @@ class VirtualFunctionCallProcessor( } } - if (children.size == 1) { - children.head - } else { - TreeConditionalElement(children) + children.size match { + case 0 ⇒ None + case 1 ⇒ Some(children.head) + case _ ⇒ Some(TreeConditionalElement(children)) } } @@ -134,7 +139,7 @@ class VirtualFunctionCallProcessor( case _: NonVirtualFunctionCall[V] ⇒ StringConstancyInformation(DYNAMIC, "*") case StringConst(_, value) ⇒ StringConstancyInformation(CONSTANT, value) // Next case is for an append call as argument to append - case _: VirtualFunctionCall[V] ⇒ processAssignment(assign, stmts).get.reduce() + case _: VirtualFunctionCall[V] ⇒ processAssignment(assign, stmts, cfg).get.reduce() case be: BinaryExpr[V] ⇒ val possibleString = ExprHandler.classNameToPossibleString( be.left.asVar.value.getClass.getSimpleName diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 0e9c3042d6..2349a15913 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -43,11 +43,17 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { c match { case Some(child) ⇒ val reduced = reduceAcc(child) - StringConstancyInformation( + // Do not consider an empty constructor as a CONSTANT value (otherwise + // PARTIALLY_CONSTANT will result when DYNAMIC is required) + val level = if (sci.possibleStrings == "") { + reduced.constancyLevel + } else { StringConstancyLevel.determineForConcat( sci.constancyLevel, reduced.constancyLevel - ), - sci.possibleStrings + reduced.possibleStrings + ) + } + StringConstancyInformation( + level, sci.possibleStrings + reduced.possibleStrings ) case None ⇒ sci } @@ -67,12 +73,12 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { val seen = mutable.Map[StringConstancyInformation, Boolean]() val unique = ListBuffer[TreeElement]() children.foreach { - case next @ TreeValueElement(_, sci) ⇒ + case next@TreeValueElement(_, sci) ⇒ if (!seen.contains(sci)) { seen += (sci → true) unique.append(next) } - case loopElement: TreeLoopElement ⇒ unique.append(loopElement) + case loopElement: TreeLoopElement ⇒ unique.append(loopElement) case condElement: TreeConditionalElement ⇒ unique.append(condElement) } unique @@ -85,7 +91,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { subtree match { case TreeConditionalElement(cs) ⇒ cs.foreach { - case nextC @ TreeConditionalElement(subChildren) ⇒ + case nextC@TreeConditionalElement(subChildren) ⇒ simplifyAcc(nextC) subChildren.foreach(subtree.children.append(_)) subtree.children.-=(nextC) @@ -115,7 +121,6 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { * * @return This function modifies `this` tree and returns this instance, e.g., for chaining * commands. - * * @note Applying this function changes the representation of the tree but not produce a * semantically different tree! Executing this function prior to [[reduce()]] simplifies * its stringified representation. @@ -128,7 +133,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { def getLeafs: Array[TreeValueElement] = { def leafsAcc(root: TreeElement, leafs: ArrayBuffer[TreeValueElement]): Unit = { root match { - case TreeLoopElement(c, _) ⇒ leafsAcc(c, leafs) + case TreeLoopElement(c, _) ⇒ leafsAcc(c, leafs) case TreeConditionalElement(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) case TreeValueElement(c, _) ⇒ if (c.isDefined) { @@ -157,7 +162,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { * cannot be determined, set it to [[None]]. */ case class TreeLoopElement( - child: TreeElement, + child: TreeElement, numLoopIterations: Option[Int] ) extends TreeElement(ListBuffer(child)) @@ -171,7 +176,7 @@ case class TreeLoopElement( * a `TreeConditionalElement` that has no children is regarded as an invalid tree in this sense! */ case class TreeConditionalElement( - override val children: ListBuffer[TreeElement] + override val children: ListBuffer[TreeElement], ) extends TreeElement(children) /** @@ -186,10 +191,10 @@ case class TreeConditionalElement( */ case class TreeValueElement( var child: Option[TreeElement], - sci: StringConstancyInformation + sci: StringConstancyInformation ) extends TreeElement( child match { case Some(c) ⇒ ListBuffer(c) - case None ⇒ ListBuffer() + case None ⇒ ListBuffer() } ) From 45323b16217faec44f2169b263ec4db4083e9f59 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:27:22 +0100 Subject: [PATCH 012/316] Extended the StringTree construction to cover another inner if-else block structure (see added test method). --- .../string_definition/TestMethods.java | 24 +++++++++++-- .../expr_processing/ExprHandler.scala | 32 +++++++++++++++++ .../NewStringBuilderProcessor.scala | 35 +++++++++++++++---- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 92d0afaa50..3bbae7b20f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -166,7 +166,7 @@ public void multipleDefSites(int value) { @StringDefinitions( value = "if-else control structure which append to a string builder with an int expr", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(x | [AnIntegerValue])" + expectedStrings = "([AnIntegerValue] | x)" ) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb = new StringBuilder(); @@ -182,7 +182,7 @@ public void ifElseWithStringBuilderWithIntExpr() { @StringDefinitions( value = "if-else control structure which append to a string builder with an int", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(x | [AnIntegerValue])" + expectedStrings = "([AnIntegerValue] | x)" ) public void ifElseWithStringBuilderWithConstantInt() { StringBuilder sb = new StringBuilder(); @@ -214,7 +214,7 @@ public void ifElseWithStringBuilder1() { @StringDefinitions( value = "if-else control structure which append to a string builder", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b | c)" + expectedStrings = "a(c | b)" ) public void ifElseWithStringBuilder2() { StringBuilder sb = new StringBuilder("a"); @@ -227,6 +227,24 @@ public void ifElseWithStringBuilder2() { analyzeString(sb.toString()); } + @StringDefinitions( + value = "if-else control structure which append to a string builder multiple times", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(yz | bc)" + ) + public void ifElseWithStringBuilder3() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("b"); + sb.append("c"); + } else { + sb.append("y"); + sb.append("z"); + } + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index e2ba0154b8..2aceab041f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -8,6 +8,7 @@ import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.fpcf.string_definition.properties.TreeValueElement import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.Expr @@ -106,6 +107,37 @@ class ExprHandler(p: SomeProject, m: Method) { )) } + /** + * chainDefSites takes the given definition sites, processes them from the first to the last + * element and chains the resulting trees together. That means, the first definition site is + * evaluated, its child becomes the evaluated tree of the second definition site and so on. + * Consequently, a [[StringTree]] will result where each element has only one child (except the + * leaf). + * + * @param defSites The definition sites to chain. + * @return Returns either a [[StringTree]] or `None` in case `defSites` is empty or its head + * points to a definition site that cannot be processed. + */ + def chainDefSites(defSites: List[Int]): Option[StringTree] = { + if (defSites.isEmpty) { + return None + } + + val parent = processDefSite(defSites.head) + parent match { + case Some(tree) ⇒ tree match { + case tve: TreeValueElement ⇒ tve.child = chainDefSites(defSites.tail) + case tree: StringTree ⇒ + chainDefSites(defSites.tail) match { + case Some(child) ⇒ tree.children.append(child) + case _ ⇒ + } + } + case None ⇒ None + } + parent + } + } object ExprHandler { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index a2e200ca34..3c57dbd5bc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.expr_processing +import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet @@ -17,6 +18,7 @@ import org.opalj.tac.New import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.TACStmts +import scala.collection.mutable import scala.collection.mutable.ListBuffer /** @@ -40,7 +42,7 @@ class NewStringBuilderProcessor( assignment.expr match { case _: New ⇒ val useSites = assignment.targetVar.usedBy.filter(!ignore.contains(_)) - val (inits, nonInits) = getInitsAndNonInits(useSites, stmts) + val (inits, nonInits) = getInitsAndNonInits(useSites, stmts, cfg) val initTreeNodes = ListBuffer[StringTree]() val nonInitTreeNodes = ListBuffer[StringTree]() @@ -65,8 +67,8 @@ class NewStringBuilderProcessor( )) } - nonInits.foreach { next ⇒ - val tree = exprHandler.processDefSite(next) + nonInits.foreach { nextBlockValues ⇒ + val tree = exprHandler.chainDefSites(nextBlockValues) if (tree.isDefined) { nonInitTreeNodes.append(tree.get) } @@ -110,10 +112,10 @@ class NewStringBuilderProcessor( * @return */ private def getInitsAndNonInits( - useSites: IntTrieSet, stmts: Array[Stmt[V]] - ): (List[Int], List[Int]) = { + useSites: IntTrieSet, stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]] + ): (List[Int], List[List[Int]]) = { val inits = ListBuffer[Int]() - val nonInits = ListBuffer[Int]() + var nonInits = ListBuffer[Int]() useSites.foreach { next ⇒ stmts(next) match { // Constructors are identified by the "init" method and assignments (ExprStmts, in @@ -123,7 +125,26 @@ class NewStringBuilderProcessor( case _ ⇒ nonInits.append(next) } } - (inits.toList, nonInits.toList) + // Sort in descending order to enable correct grouping in the next step + nonInits = nonInits.sorted.reverse + + // Next, group all non inits into lists depending on their basic block in the CFG + val blocks = mutable.LinkedHashMap[BasicBlock, ListBuffer[Int]]() + nonInits.foreach { next ⇒ + val nextBlock = cfg.bb(next) + val parentBlock = nextBlock.successors.filter { + case bb: BasicBlock ⇒ blocks.contains(bb) + case _ ⇒ false + } + if (parentBlock.nonEmpty) { + blocks(parentBlock.head.asBasicBlock).append(next) + } else { + blocks += (nextBlock → ListBuffer[Int](next)) + } + } + + // Sort the lists in ascending order as this is more intuitive + (inits.toList, blocks.map(_._2.toList.sorted).toList) } } From e3f2178ac5891de1ffc7b1cb06e76ea42ec398d1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:27:48 +0100 Subject: [PATCH 013/316] The string approximation can now recognize the case when an element is within a loop. A simple test was added, too. --- .../string_definition/TestMethods.java | 14 +++ .../expr_processing/ExprHandler.scala | 89 ++++++++++++++++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 3bbae7b20f..bfb026d883 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -236,6 +236,7 @@ public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); if (i % 2 == 0) { + // TODO: Extend the case with three append calls per block sb.append("b"); sb.append("c"); } else { @@ -245,6 +246,19 @@ public void ifElseWithStringBuilder3() { analyzeString(sb.toString()); } + @StringDefinitions( + value = "simple for loop with knows bounds", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b)^∞" + ) + public void simpleForLoopWithKnownBounds() { + StringBuilder sb = new StringBuilder("a"); + for (int i = 0; i < 10; i++) { + sb.append("b"); + } + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 2aceab041f..4ee3de8e04 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -3,20 +3,26 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.Method import org.opalj.br.analyses.SomeProject +import org.opalj.br.cfg.CFG import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.fpcf.string_definition.properties.TreeLoopElement import org.opalj.fpcf.string_definition.properties.TreeValueElement import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.ExprStmt +import org.opalj.tac.Goto +import org.opalj.tac.If import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.Stmt import org.opalj.tac.StringConst +import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import scala.collection.mutable.ListBuffer @@ -79,7 +85,12 @@ class ExprHandler(p: SomeProject, m: Method) { case _ ⇒ exprProcessor.processExpr(expr, ctxStmts, cfg, processedDefSites.toList) } - subtree + + if (subtree.isDefined && ExprHandler.isWithinLoop(defSite, cfg)) { + Some(TreeLoopElement(subtree.get, None)) + } else { + subtree + } } /** @@ -152,6 +163,82 @@ object ExprHandler { */ def apply(p: SomeProject, m: Method): ExprHandler = new ExprHandler(p, m) + /** + * Determines the successor [[Goto]] element for the given definition site, if present. + * + * @param defSite The definition site to check. + * @param cfg The control flow graph which is required for that operation. + * @return Either returns the corresponding [[Goto]] element or `None` in case there is non + * following the given site. + */ + private def getSuccessorGoto(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Option[Goto] = { + val successorBlocks = cfg.bb(defSite).successors.filter(_.isBasicBlock) + val successorDefSites = ListBuffer[Int]() + var goto: Option[Goto] = None + + successorBlocks.foreach { next ⇒ + for (i ← next.asBasicBlock.startPC to next.asBasicBlock.endPC) { + cfg.code.instructions(i) match { + case gt: Goto ⇒ goto = Some(gt) + case _ ⇒ if (i > defSite) successorDefSites.append(i) + } + } + } + if (goto.isDefined) { + goto + } else { + val successor = successorDefSites.map(getSuccessorGoto(_, cfg)).filter(_.isDefined) + if (successor.nonEmpty && successor.head.isDefined) { + successor.head + } else { + None + } + } + } + + /** + * Determines the if statement that belongs to the given `goto`. + * + * @param goto The [[Goto]] for which to determine the corresponding [[If]]. Note that the + * `goto` is not required to have a corresponding [[If]]. + * @param cfg The control flow graph which is required for that operation. + * @return Either returns the corresponding [[If]] or `None` if there is no such [[If]]. + */ + private def getIfOfGoto(goto: Goto, cfg: CFG[Stmt[V], TACStmts[V]]): Option[If[V]] = { + cfg.code.instructions(goto.targetStmt) match { + case a: Assignment[V] ⇒ + val possibleIfsSites = a.targetVar.usedBy + possibleIfsSites.filter(cfg.code.instructions(_).isInstanceOf[If[V]]).map { + cfg.code.instructions(_).asIf + }.headOption + case _ ⇒ None + } + } + + /** + * Checks whether the given definition site is within a loop. + * + * @param defSite The definition site to check. + * @param cfg The control flow graph which is required for that operation. + * @return Returns `true` if the given site resides within a loop and `false` otherwise. + */ + def isWithinLoop(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + val succGoto = getSuccessorGoto(defSite, cfg) + if (succGoto.isEmpty) { + false + } else { + val correspondingIf = getIfOfGoto(succGoto.get, cfg) + if (correspondingIf.isEmpty) { + false + } else { + // To be within a loop, the definition site must be within the if and goto + val posIf = cfg.code.instructions.indexOf(correspondingIf.get) + val posGoto = cfg.code.instructions.indexOf(succGoto.get) + defSite > posIf && defSite < posGoto + } + } + } + /** * Checks whether an expression contains a call to [[StringBuilder.toString]]. * From 20e996f794daf6334f0a85ac6e1720c0e21599f3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:29:34 +0100 Subject: [PATCH 014/316] Within the string definition analysis, the asterisk symbol now marks that some string may occur >= 0 times. The string "\w" now marks that a string or part of it can be an arbitrary string. --- .../string_definition/TestMethods.java | 25 +++++++++++++++---- .../NonVirtualFunctionCallProcessor.scala | 3 ++- .../VirtualFunctionCallProcessor.scala | 10 +++++--- .../StringConstancyInformation.scala | 10 ++++++++ .../properties/StringTree.scala | 19 +++++++------- 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index bfb026d883..612ddcc592 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -83,7 +83,7 @@ public void advStringConcat() { @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "*" + expectedStrings = "\\w" ) public void fromFunctionCall() { String className = getStringBuilderClassName(); @@ -93,7 +93,7 @@ public void fromFunctionCall() { @StringDefinitions( value = "constant string + string from function call => PARTIALLY_CONSTANT", expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "java.lang.*" + expectedStrings = "java.lang.\\w" ) public void fromConstantAndFunctionCall() { String className = "java.lang."; @@ -137,7 +137,7 @@ public void multipleConstantDefSites(boolean cond) { value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(java.lang.System | java.lang.* | * | java.lang.Object)" + expectedStrings = "(\\w | java.lang.System | java.lang.\\w | java.lang.Object)" ) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -247,9 +247,10 @@ public void ifElseWithStringBuilder3() { } @StringDefinitions( - value = "simple for loop with knows bounds", + value = "simple for loop with known bounds", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)^∞" + // Currently, the analysis does not support determining loop ranges => a(b)* + expectedStrings = "a(b)*" ) public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); @@ -259,6 +260,20 @@ public void simpleForLoopWithKnownBounds() { analyzeString(sb.toString()); } + // @StringDefinitions( + // value = "simple for loop with unknown bounds", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(b)*" + // ) + // public void simpleForLoopWithUnknownBounds() { + // int limit = new Random().nextInt(); + // StringBuilder sb = new StringBuilder("a"); + // for (int i = 0; i < limit; i++) { + // sb.append("b"); + // } + // analyzeString(sb.toString()); + // } + // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala index 8ee4cf4ed8..2826297e54 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -4,6 +4,7 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.UnknownWordSymbol import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.TreeValueElement @@ -56,7 +57,7 @@ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { ): Option[StringTree] = { expr match { case _: NonVirtualFunctionCall[V] ⇒ Some(TreeValueElement( - None, StringConstancyInformation(DYNAMIC, "*") + None, StringConstancyInformation(DYNAMIC, UnknownWordSymbol) )) case _ ⇒ None } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index e94974384e..4ccdf69ec1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -4,6 +4,7 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.UnknownWordSymbol import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC import org.opalj.fpcf.string_definition.properties.StringTree @@ -136,10 +137,13 @@ class VirtualFunctionCallProcessor( val defAssignment = call.params.head.asVar.definedBy.head val assign = stmts(defAssignment).asAssignment val sci = assign.expr match { - case _: NonVirtualFunctionCall[V] ⇒ StringConstancyInformation(DYNAMIC, "*") - case StringConst(_, value) ⇒ StringConstancyInformation(CONSTANT, value) + case _: NonVirtualFunctionCall[V] ⇒ + StringConstancyInformation(DYNAMIC, UnknownWordSymbol) + case StringConst(_, value) ⇒ + StringConstancyInformation(CONSTANT, value) // Next case is for an append call as argument to append - case _: VirtualFunctionCall[V] ⇒ processAssignment(assign, stmts, cfg).get.reduce() + case _: VirtualFunctionCall[V] ⇒ + processAssignment(assign, stmts, cfg).get.reduce() case be: BinaryExpr[V] ⇒ val possibleString = ExprHandler.classNameToPossibleString( be.left.asVar.value.getClass.getSimpleName diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 6ee8583423..99bf758419 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -8,3 +8,13 @@ package org.opalj.fpcf.string_definition.properties case class StringConstancyInformation( constancyLevel: StringConstancyLevel.Value, possibleStrings: String ) + +object StringConstancyInformation { + + /** + * This string stores the value that is to be used when a string is dynamic, i.e., can have + * arbitrary values. + */ + val UnknownWordSymbol: String = "\\w" + +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 2349a15913..94db945389 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -33,10 +33,10 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { case TreeLoopElement(c, nli) ⇒ val reduced = reduceAcc(c) - val times = if (nli.isDefined) nli.get.toString else "∞" + val times = if (nli.isDefined) nli.get.toString else "*" StringConstancyInformation( reduced.constancyLevel, - s"(${reduced.possibleStrings})^$times" + s"(${reduced.possibleStrings})$times" ) case TreeValueElement(c, sci) ⇒ @@ -73,12 +73,12 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { val seen = mutable.Map[StringConstancyInformation, Boolean]() val unique = ListBuffer[TreeElement]() children.foreach { - case next@TreeValueElement(_, sci) ⇒ + case next @ TreeValueElement(_, sci) ⇒ if (!seen.contains(sci)) { seen += (sci → true) unique.append(next) } - case loopElement: TreeLoopElement ⇒ unique.append(loopElement) + case loopElement: TreeLoopElement ⇒ unique.append(loopElement) case condElement: TreeConditionalElement ⇒ unique.append(condElement) } unique @@ -91,7 +91,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { subtree match { case TreeConditionalElement(cs) ⇒ cs.foreach { - case nextC@TreeConditionalElement(subChildren) ⇒ + case nextC @ TreeConditionalElement(subChildren) ⇒ simplifyAcc(nextC) subChildren.foreach(subtree.children.append(_)) subtree.children.-=(nextC) @@ -121,6 +121,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { * * @return This function modifies `this` tree and returns this instance, e.g., for chaining * commands. + * * @note Applying this function changes the representation of the tree but not produce a * semantically different tree! Executing this function prior to [[reduce()]] simplifies * its stringified representation. @@ -133,7 +134,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { def getLeafs: Array[TreeValueElement] = { def leafsAcc(root: TreeElement, leafs: ArrayBuffer[TreeValueElement]): Unit = { root match { - case TreeLoopElement(c, _) ⇒ leafsAcc(c, leafs) + case TreeLoopElement(c, _) ⇒ leafsAcc(c, leafs) case TreeConditionalElement(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) case TreeValueElement(c, _) ⇒ if (c.isDefined) { @@ -162,7 +163,7 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { * cannot be determined, set it to [[None]]. */ case class TreeLoopElement( - child: TreeElement, + child: TreeElement, numLoopIterations: Option[Int] ) extends TreeElement(ListBuffer(child)) @@ -191,10 +192,10 @@ case class TreeConditionalElement( */ case class TreeValueElement( var child: Option[TreeElement], - sci: StringConstancyInformation + sci: StringConstancyInformation ) extends TreeElement( child match { case Some(c) ⇒ ListBuffer(c) - case None ⇒ ListBuffer() + case None ⇒ ListBuffer() } ) From 0359ab4a99c9ba962e37349a6aa503d39d29099f Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:33:27 +0100 Subject: [PATCH 015/316] 1) Changed the structure of the tree (they consist of five elements from now on) and 2) changed the pattern that represents the possible strings (*, \w). --- .../string_definition/TestMethods.java | 28 +-- .../string_definition/StringDefinitions.java | 2 +- .../LocalStringDefinitionAnalysis.scala | 6 +- .../expr_processing/ArrayLoadProcessor.scala | 35 ++- .../expr_processing/ExprHandler.scala | 57 +++-- .../NewStringBuilderProcessor.scala | 76 ++++--- .../NonVirtualFunctionCallProcessor.scala | 8 +- .../StringConstProcessor.scala | 5 +- .../VirtualFunctionCallProcessor.scala | 30 +-- .../properties/StringConstancyProperty.scala | 6 +- .../StringConstancyInformation.scala | 5 + .../properties/StringTree.scala | 201 ++++++++++-------- .../properties/package.scala | 2 +- 13 files changed, 248 insertions(+), 213 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 612ddcc592..d6c2857074 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -137,7 +137,7 @@ public void multipleConstantDefSites(boolean cond) { value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(\\w | java.lang.System | java.lang.\\w | java.lang.Object)" + expectedStrings = "(java.lang.\\w | \\w | java.lang.System | java.lang.Object)" ) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -260,19 +260,19 @@ public void simpleForLoopWithKnownBounds() { analyzeString(sb.toString()); } - // @StringDefinitions( - // value = "simple for loop with unknown bounds", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(b)*" - // ) - // public void simpleForLoopWithUnknownBounds() { - // int limit = new Random().nextInt(); - // StringBuilder sb = new StringBuilder("a"); - // for (int i = 0; i < limit; i++) { - // sb.append("b"); - // } - // analyzeString(sb.toString()); - // } + @StringDefinitions( + value = "simple for loop with unknown bounds", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b)*" + ) + public void simpleForLoopWithUnknownBounds() { + int limit = new Random().nextInt(); + StringBuilder sb = new StringBuilder("a"); + for (int i = 0; i < limit; i++) { + sb.append("b"); + } + analyzeString(sb.toString()); + } // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index ed1d701d8e..ffc86f4727 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -36,7 +36,7 @@ /** * A regexp like string that describes the elements that are expected. For the rules, refer to - * {@link org.opalj.fpcf.string_definition.properties.TreeElement}. + * {@link org.opalj.fpcf.string_definition.properties.StringTreeElement}. * For example, "(* | (hello | world)^5)" describes a string which can 1) either be any string * or 2) a five time concatenation of "hello" and/or "world". */ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 1358146079..28a57c2e81 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -12,7 +12,7 @@ import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.analyses.string_definition.expr_processing.ExprHandler import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeConditionalElement +import org.opalj.fpcf.string_definition.properties.StringTreeCond import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt @@ -56,12 +56,12 @@ class LocalStringDefinitionAnalysis( if (treeElements.size == 1) { subtrees.append(treeElements.head) } else { - subtrees.append(TreeConditionalElement(treeElements.to[ListBuffer])) + subtrees.append(StringTreeCond(treeElements.to[ListBuffer])) } } val finalTree = if (subtrees.size == 1) subtrees.head else - TreeConditionalElement(subtrees.to[ListBuffer]) + StringTreeCond(subtrees.to[ListBuffer]) Result(data, StringConstancyProperty(finalTree)) } // If not a call to StringBuilder.toString, then we deal with pure strings else { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index 95365f26c8..0e9b6187a1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -3,8 +3,8 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeConditionalElement -import org.opalj.fpcf.string_definition.properties.TreeElement +import org.opalj.fpcf.string_definition.properties.StringTreeElement +import org.opalj.fpcf.string_definition.properties.StringTreeOr import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore import org.opalj.tac.Assignment @@ -58,26 +58,25 @@ class ArrayLoadProcessor( ): Option[StringTree] = { expr match { case al: ArrayLoad[V] ⇒ - val children = ListBuffer[TreeElement]() + val children = ListBuffer[StringTreeElement]() // Loop over all possible array values - al.arrayRef.asVar.definedBy.foreach { defSite ⇒ - if (!ignore.contains(defSite)) { - val arrDecl = stmts(defSite) - arrDecl.asAssignment.targetVar.usedBy.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int ⇒ - // Actually, definedBy should contain only one element but for the sake - // of completion, loop over all - // TODO: If not, the tree construction has to be modified - val arrValues = stmts(f).asArrayStore.value.asVar.definedBy.map { - exprHandler.processDefSite _ - }.filter(_.isDefined).map(_.get) - children.appendAll(arrValues) - } + al.arrayRef.asVar.definedBy.filter(!ignore.contains(_)).foreach { next ⇒ + val arrDecl = stmts(next) + arrDecl.asAssignment.targetVar.usedBy.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + val arrValues = stmts(f).asArrayStore.value.asVar.definedBy.map { + exprHandler.processDefSite _ + }.filter(_.isDefined).map(_.get) + children.appendAll(arrValues) } } - Some(TreeConditionalElement(children)) + if (children.nonEmpty) { + Some(StringTreeOr(children)) + } else { + None + } case _ ⇒ None } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 4ee3de8e04..09fcb7d0cd 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -8,9 +8,9 @@ import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeConditionalElement -import org.opalj.fpcf.string_definition.properties.TreeLoopElement -import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.fpcf.string_definition.properties.StringTreeConcat +import org.opalj.fpcf.string_definition.properties.StringTreeOr +import org.opalj.fpcf.string_definition.properties.StringTreeRepetition import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.Expr @@ -87,7 +87,7 @@ class ExprHandler(p: SomeProject, m: Method) { } if (subtree.isDefined && ExprHandler.isWithinLoop(defSite, cfg)) { - Some(TreeLoopElement(subtree.get, None)) + Some(StringTreeRepetition(subtree.get, None)) } else { subtree } @@ -104,8 +104,7 @@ class ExprHandler(p: SomeProject, m: Method) { * takes into consideration only those values from `processDefSite` that are not `None`. * Furthermore, this function assumes that different definition sites originate from * control flow statements; thus, this function returns a tree with a - * [[TreeConditionalElement]] as root and - * each definition site as a child. + * [[StringTreeOr]] as root and each definition site as a child. */ def processDefSites(defSites: IntTrieSet): Option[StringTree] = defSites.size match { @@ -113,40 +112,33 @@ class ExprHandler(p: SomeProject, m: Method) { case 1 ⇒ processDefSite(defSites.head) case _ ⇒ val processedSites = defSites.filter(_ >= 0).map(processDefSite _) - Some(TreeConditionalElement( + Some(StringTreeOr( processedSites.filter(_.isDefined).map(_.get).to[ListBuffer] )) } /** - * chainDefSites takes the given definition sites, processes them from the first to the last - * element and chains the resulting trees together. That means, the first definition site is - * evaluated, its child becomes the evaluated tree of the second definition site and so on. - * Consequently, a [[StringTree]] will result where each element has only one child (except the - * leaf). + * concatDefSites takes the given definition sites, processes them from the first to the last + * element and chains the resulting trees together. That means, a + * [[StringTreeConcat]] element is returned with one child for each def site in `defSites`. * - * @param defSites The definition sites to chain. - * @return Returns either a [[StringTree]] or `None` in case `defSites` is empty or its head - * points to a definition site that cannot be processed. + * @param defSites The definition sites to concat / process. + * @return Returns either a [[StringTree]] or `None` in case `defSites` is empty (or does not + * contain processable def sites). */ - def chainDefSites(defSites: List[Int]): Option[StringTree] = { + def concatDefSites(defSites: List[Int]): Option[StringTree] = { if (defSites.isEmpty) { return None } - val parent = processDefSite(defSites.head) - parent match { - case Some(tree) ⇒ tree match { - case tve: TreeValueElement ⇒ tve.child = chainDefSites(defSites.tail) - case tree: StringTree ⇒ - chainDefSites(defSites.tail) match { - case Some(child) ⇒ tree.children.append(child) - case _ ⇒ - } - } - case None ⇒ None + val children = defSites.map(processDefSite).filter(_.isDefined).map(_.get) + if (children.isEmpty) { + None + } else if (children.size == 1) { + Some(children.head) + } else { + Some(StringTreeConcat(children.to[ListBuffer])) } - parent } } @@ -206,6 +198,7 @@ object ExprHandler { */ private def getIfOfGoto(goto: Goto, cfg: CFG[Stmt[V], TACStmts[V]]): Option[If[V]] = { cfg.code.instructions(goto.targetStmt) match { + case i: If[V] ⇒ Some(i) case a: Assignment[V] ⇒ val possibleIfsSites = a.targetVar.usedBy possibleIfsSites.filter(cfg.code.instructions(_).isInstanceOf[If[V]]).map { @@ -223,17 +216,17 @@ object ExprHandler { * @return Returns `true` if the given site resides within a loop and `false` otherwise. */ def isWithinLoop(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { - val succGoto = getSuccessorGoto(defSite, cfg) - if (succGoto.isEmpty) { + val successorGoto = getSuccessorGoto(defSite, cfg) + if (successorGoto.isEmpty) { false } else { - val correspondingIf = getIfOfGoto(succGoto.get, cfg) + val correspondingIf = getIfOfGoto(successorGoto.get, cfg) if (correspondingIf.isEmpty) { false } else { // To be within a loop, the definition site must be within the if and goto val posIf = cfg.code.instructions.indexOf(correspondingIf.get) - val posGoto = cfg.code.instructions.indexOf(succGoto.get) + val posGoto = cfg.code.instructions.indexOf(successorGoto.get) defSite > posIf && defSite < posGoto } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 3c57dbd5bc..f426ccf392 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -6,12 +6,12 @@ import org.opalj.br.cfg.CFG import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT import org.opalj.tac.Stmt import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeConditionalElement -import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.fpcf.string_definition.properties.StringTreeConcat +import org.opalj.fpcf.string_definition.properties.StringTreeConst +import org.opalj.fpcf.string_definition.properties.StringTreeOr +import org.opalj.fpcf.string_definition.properties.StringTreeRepetition import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.New @@ -46,50 +46,70 @@ class NewStringBuilderProcessor( val initTreeNodes = ListBuffer[StringTree]() val nonInitTreeNodes = ListBuffer[StringTree]() - inits.foreach { next ⇒ + inits.sorted.foreach { next ⇒ val toProcess = stmts(next) match { case init: NonVirtualMethodCall[V] if init.params.nonEmpty ⇒ init.params.head.asVar.definedBy case assignment: Assignment[V] ⇒ - assignment.expr.asVirtualFunctionCall.receiver.asVar.definedBy + val vfc = assignment.expr.asVirtualFunctionCall + var defs = vfc.receiver.asVar.definedBy + if (vfc.params.nonEmpty) { + vfc.params.head.asVar.definedBy.foreach(defs += _) + } + defs case _ ⇒ EmptyIntTrieSet } - exprHandler.processDefSites(toProcess) match { - case Some(toAppend) ⇒ initTreeNodes.append(toAppend) - case None ⇒ + val processed = if (toProcess.size == 1) { + val intermRes = exprHandler.processDefSite(toProcess.head) + if (intermRes.isDefined) intermRes else None + } else { + val children = toProcess.map(exprHandler.processDefSite _). + filter(_.isDefined).map(_.get) + children.size match { + case 0 ⇒ None + case 1 ⇒ Some(children.head) + case _ ⇒ Some(StringTreeConcat(children.to[ListBuffer])) + } + } + if (processed.isDefined) { + initTreeNodes.append(processed.get) } - } - // No argument to constructor was passed => empty string with nonInits as child - if (initTreeNodes.isEmpty) { - initTreeNodes.append(TreeValueElement( - None, StringConstancyInformation(CONSTANT, "") - )) } - nonInits.foreach { nextBlockValues ⇒ - val tree = exprHandler.chainDefSites(nextBlockValues) - if (tree.isDefined) { - nonInitTreeNodes.append(tree.get) + nonInits.foreach { next ⇒ + val subtree = exprHandler.concatDefSites(next) + if (subtree.isDefined) { + nonInitTreeNodes.append(subtree.get) } } + if (initTreeNodes.isEmpty && nonInitTreeNodes.isEmpty) { + return None + } + + // Append nonInitTreeNodes to initTreeNodes (as children) if (nonInitTreeNodes.nonEmpty) { - initTreeNodes.foreach { next ⇒ - val toAppend = nonInitTreeNodes.size match { - case 1 ⇒ nonInitTreeNodes.head - case _ ⇒ TreeConditionalElement(nonInitTreeNodes) - } - next match { - case tve: TreeValueElement ⇒ tve.child = Some(toAppend) - case _ ⇒ next.children.append(toAppend) + val toAppend = nonInitTreeNodes.size match { + case 1 ⇒ nonInitTreeNodes.head + case _ ⇒ StringTreeOr(nonInitTreeNodes) + } + if (initTreeNodes.isEmpty) { + initTreeNodes.append(toAppend) + } else { + initTreeNodes.zipWithIndex.foreach { + case (rep: StringTreeRepetition, _) ⇒ rep.child = toAppend + // We cannot add to a constant element => slightly rearrange the tree + case (const: StringTreeConst, index) ⇒ + initTreeNodes(index) = StringTreeConcat(ListBuffer(const, toAppend)) + case (next, _) ⇒ next.children.append(toAppend) } } } initTreeNodes.size match { case 1 ⇒ Some(initTreeNodes.head) - case _ ⇒ Some(TreeConditionalElement(initTreeNodes)) + case _ ⇒ Some(StringTreeOr(initTreeNodes)) } case _ ⇒ None } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala index 2826297e54..2822225d59 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala @@ -7,7 +7,7 @@ import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.UnknownWordSymbol import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.fpcf.string_definition.properties.StringTreeConst import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.NonVirtualFunctionCall @@ -18,7 +18,7 @@ import org.opalj.tac.TACStmts * This implementation of [[AbstractExprProcessor]] processes * [[org.opalj.tac.NonVirtualFunctionCall]] expressions. * Currently, this implementation is only a rough approximation in the sense that all - * `NonVirtualFunctionCall`s are processed by returning a [[TreeValueElement]] with no children + * `NonVirtualFunctionCall`s are processed by returning a [[StringTreeConst]] with no children * and `StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))` as a value (i.e., it does not analyze * the function call in depth). * @@ -56,8 +56,8 @@ class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { expr match { - case _: NonVirtualFunctionCall[V] ⇒ Some(TreeValueElement( - None, StringConstancyInformation(DYNAMIC, UnknownWordSymbol) + case _: NonVirtualFunctionCall[V] ⇒ Some(StringTreeConst( + StringConstancyInformation(DYNAMIC, UnknownWordSymbol) )) case _ ⇒ None } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala index 2418411a16..63ed4dd6c0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala @@ -5,7 +5,7 @@ import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.fpcf.string_definition.properties.StringTreeConst import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.Stmt @@ -53,8 +53,7 @@ class StringConstProcessor() extends AbstractExprProcessor { expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { expr match { - case strConst: StringConst ⇒ Some(TreeValueElement( - None, + case strConst: StringConst ⇒ Some(StringTreeConst( StringConstancyInformation(CONSTANT, strConst.value) )) case _ ⇒ None diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index 4ccdf69ec1..afe953a1c0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -8,9 +8,10 @@ import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.Un import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeConditionalElement -import org.opalj.fpcf.string_definition.properties.TreeElement -import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.fpcf.string_definition.properties.StringTreeConcat +import org.opalj.fpcf.string_definition.properties.StringTreeCond +import org.opalj.fpcf.string_definition.properties.StringTreeElement +import org.opalj.fpcf.string_definition.properties.StringTreeConst import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr @@ -72,7 +73,7 @@ class VirtualFunctionCallProcessor( val ps = ExprHandler.classNameToPossibleString( vfc.descriptor.returnType.toJavaClass.getSimpleName ) - Some(TreeValueElement(None, StringConstancyInformation(DYNAMIC, ps))) + Some(StringTreeConst(StringConstancyInformation(DYNAMIC, ps))) } case _ ⇒ None } @@ -83,15 +84,14 @@ class VirtualFunctionCallProcessor( */ private def processAppendCall( call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): TreeElement = { + ): StringTreeElement = { val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) val appendValue = valueOfAppendCall(call, stmts) - if (defSites.isEmpty) { - appendValue + val siblings = exprHandler.processDefSites(defSites) + if (siblings.isDefined) { + StringTreeConcat(ListBuffer[StringTreeElement](siblings.get, appendValue)) } else { - val upperTree = exprHandler.processDefSites(defSites).get - upperTree.getLeafs.foreach { _.child = Some(appendValue) } - upperTree + appendValue } } @@ -101,7 +101,7 @@ class VirtualFunctionCallProcessor( private def processToStringCall( call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { - val children = ListBuffer[TreeElement]() + val children = ListBuffer[StringTreeElement]() val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) defSites.foreach { exprHandler.processDefSite(_) match { @@ -113,7 +113,7 @@ class VirtualFunctionCallProcessor( children.size match { case 0 ⇒ None case 1 ⇒ Some(children.head) - case _ ⇒ Some(TreeConditionalElement(children)) + case _ ⇒ Some(StringTreeCond(children)) } } @@ -124,7 +124,7 @@ class VirtualFunctionCallProcessor( * @param call A function call of `StringBuilder#append`. Note that for all other methods an * [[IllegalArgumentException]] will be thrown. * @param stmts The surrounding context, e.g., the surrounding method. - * @return Returns a [[TreeValueElement]] with no children and the following value for + * @return Returns a [[StringTreeConst]] with no children and the following value for * [[StringConstancyInformation]]: For constants strings as arguments, this function * returns the string value and the level * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT]]. For @@ -133,7 +133,7 @@ class VirtualFunctionCallProcessor( */ private def valueOfAppendCall( call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] - ): TreeValueElement = { + ): StringTreeConst = { val defAssignment = call.params.head.asVar.definedBy.head val assign = stmts(defAssignment).asAssignment val sci = assign.expr match { @@ -150,7 +150,7 @@ class VirtualFunctionCallProcessor( ) StringConstancyInformation(DYNAMIC, possibleString) } - TreeValueElement(None, sci) + StringTreeConst(sci) } } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index 9040df2cd3..274c28b4e7 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -11,7 +11,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.TreeValueElement +import org.opalj.fpcf.string_definition.properties.StringTreeConst sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformation { type Self = StringConstancyProperty @@ -40,8 +40,8 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { PropertyKeyName, (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - StringConstancyProperty(TreeValueElement( - None, StringConstancyInformation(DYNAMIC, "*") + StringConstancyProperty(StringTreeConst( + StringConstancyInformation(DYNAMIC, "*") )) }, (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 99bf758419..5f10efa2bc 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -17,4 +17,9 @@ object StringConstancyInformation { */ val UnknownWordSymbol: String = "\\w" + /** + * A value to be used when the number of an element, that is repeated, is unknown. + */ + val InfiniteRepetitionSymbol: String = "*" + } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 94db945389..748fd50086 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -1,17 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.string_definition.properties +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.InfiniteRepetitionSymbol + import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ListBuffer /** - * Models the nodes and leafs of [[StringTree]]. - * TODO: Prepend "String" + * Super type for modeling nodes and leafs of [[StringTree]]s. * * @author Patrick Mell */ -sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { +sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeElement]) { /** * Accumulator / helper function for reducing a tree. @@ -19,9 +20,39 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { * @param subtree The tree (or subtree) to reduce. * @return The reduced tree. */ - private def reduceAcc(subtree: TreeElement): StringConstancyInformation = { + private def reduceAcc(subtree: StringTreeElement): StringConstancyInformation = { subtree match { - case TreeConditionalElement(c) ⇒ + case StringTreeRepetition(c, lowerBound, upperBound) ⇒ + val reduced = reduceAcc(c) + val times = if (lowerBound.isDefined && upperBound.isDefined) + (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol + StringConstancyInformation( + reduced.constancyLevel, + s"(${reduced.possibleStrings})$times" + ) + + case StringTreeConcat(cs) ⇒ + cs.map(reduceAcc).reduceLeft { (old, next) ⇒ + StringConstancyInformation( + StringConstancyLevel.determineForConcat( + old.constancyLevel, next.constancyLevel + ), + old.possibleStrings + next.possibleStrings + ) + } + + case StringTreeOr(cs) ⇒ + val reduced = cs.map(reduceAcc).reduceLeft { (old, next) ⇒ + StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral( + old.constancyLevel, next.constancyLevel + ), + old.possibleStrings+" | "+next.possibleStrings + ) + } + StringConstancyInformation(reduced.constancyLevel, s"(${reduced.possibleStrings})") + + case StringTreeCond(c) ⇒ val scis = c.map(reduceAcc) val reducedInfo = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), @@ -31,55 +62,32 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})" ) - case TreeLoopElement(c, nli) ⇒ - val reduced = reduceAcc(c) - val times = if (nli.isDefined) nli.get.toString else "*" - StringConstancyInformation( - reduced.constancyLevel, - s"(${reduced.possibleStrings})$times" - ) - - case TreeValueElement(c, sci) ⇒ - c match { - case Some(child) ⇒ - val reduced = reduceAcc(child) - // Do not consider an empty constructor as a CONSTANT value (otherwise - // PARTIALLY_CONSTANT will result when DYNAMIC is required) - val level = if (sci.possibleStrings == "") { - reduced.constancyLevel - } else { - StringConstancyLevel.determineForConcat( - sci.constancyLevel, reduced.constancyLevel - ) - } - StringConstancyInformation( - level, sci.possibleStrings + reduced.possibleStrings - ) - case None ⇒ sci - } + case StringTreeConst(sci) ⇒ sci } } /** - * This function removes duplicate [[TreeValueElement]]s from a given list. In this context, two - * elements are equal if their [[TreeValueElement.sci]] information is equal. + * This function removes duplicate [[StringTreeConst]]s from a given list. In this + * context, two elements are equal if their [[StringTreeConst.sci]] information are equal. * * @param children The children from which to remove duplicates. - * @return Returns a list of [[TreeElement]] with unique elements. + * @return Returns a list of [[StringTreeElement]] with unique elements. */ private def removeDuplicateTreeValues( - children: ListBuffer[TreeElement] - ): ListBuffer[TreeElement] = { + children: ListBuffer[StringTreeElement] + ): ListBuffer[StringTreeElement] = { val seen = mutable.Map[StringConstancyInformation, Boolean]() - val unique = ListBuffer[TreeElement]() + val unique = ListBuffer[StringTreeElement]() children.foreach { - case next @ TreeValueElement(_, sci) ⇒ + case next @ StringTreeConst(sci) ⇒ if (!seen.contains(sci)) { seen += (sci → true) unique.append(next) } - case loopElement: TreeLoopElement ⇒ unique.append(loopElement) - case condElement: TreeConditionalElement ⇒ unique.append(condElement) + case loop: StringTreeRepetition ⇒ unique.append(loop) + case concat: StringTreeConcat ⇒ unique.append(concat) + case or: StringTreeOr ⇒ unique.append(or) + case cond: StringTreeCond ⇒ unique.append(cond) } unique } @@ -89,9 +97,9 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { */ private def simplifyAcc(subtree: StringTree): StringTree = { subtree match { - case TreeConditionalElement(cs) ⇒ + case StringTreeOr(cs) ⇒ cs.foreach { - case nextC @ TreeConditionalElement(subChildren) ⇒ + case nextC @ StringTreeOr(subChildren) ⇒ simplifyAcc(nextC) subChildren.foreach(subtree.children.append(_)) subtree.children.-=(nextC) @@ -115,13 +123,13 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { /** * Simplifies this tree. Currently, this means that when a (sub) tree has a - * [[TreeConditionalElement]] as root, ''r'', and a child, ''c'' (or several children) which is - * a [[TreeConditionalElement]] as well, that ''c'' is attached as a direct child of ''r'' (the - * child [[TreeConditionalElement]] under which ''c'' was located is then removed safely). + * [[StringTreeCond]] as root, ''r'', and a child, ''c'' (or several children) + * which is a [[StringTreeCond]] as well, that ''c'' is attached as a direct child + * of ''r'' (the child [[StringTreeCond]] under which ''c'' was located is then + * removed safely). * * @return This function modifies `this` tree and returns this instance, e.g., for chaining * commands. - * * @note Applying this function changes the representation of the tree but not produce a * semantically different tree! Executing this function prior to [[reduce()]] simplifies * its stringified representation. @@ -131,21 +139,18 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { /** * @return Returns all leaf elements of this instance. */ - def getLeafs: Array[TreeValueElement] = { - def leafsAcc(root: TreeElement, leafs: ArrayBuffer[TreeValueElement]): Unit = { + def getLeafs: Array[StringTreeConst] = { + def leafsAcc(root: StringTreeElement, leafs: ArrayBuffer[StringTreeConst]): Unit = { root match { - case TreeLoopElement(c, _) ⇒ leafsAcc(c, leafs) - case TreeConditionalElement(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) - case TreeValueElement(c, _) ⇒ - if (c.isDefined) { - leafsAcc(c.get, leafs) - } else { - leafs.append(root.asInstanceOf[TreeValueElement]) - } + case StringTreeRepetition(c, _, _) ⇒ leafsAcc(c, leafs) + case StringTreeConcat(c) ⇒ c.foreach(leafsAcc(_, leafs)) + case StringTreeOr(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) + case StringTreeCond(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) + case stc: StringTreeConst ⇒ leafs.append(stc) } } - val leafs = ArrayBuffer[TreeValueElement]() + val leafs = ArrayBuffer[StringTreeConst]() leafsAcc(this, leafs) leafs.toArray } @@ -153,49 +158,63 @@ sealed abstract class TreeElement(val children: ListBuffer[TreeElement]) { } /** - * TreeLoopElement models loops with a [[StringTree]]. `TreeLoopElement`s are supposed to have - * either at lease one other `TreeLoopElement`, `TreeConditionalElement`, or a [[TreeValueElement]] - * as children . A tree with a `TreeLoopElement` that has no children is regarded as an invalid tree - * in this sense!
+ * [[StringTreeRepetition]] models repetitive elements within a [[StringTree]], such as loops + * or recursion. [[StringTreeRepetition]] are required to have a child. A tree with a + * [[StringTreeRepetition]] that has no child is regarded as an invalid tree!
* - * `numLoopIterations` indicates how often the loop iterates. For some loops, this can be statically - * computed - in this case set `numLoopIterations` to that value. When the number of loop iterations - * cannot be determined, set it to [[None]]. + * `lowerBound` and `upperBound` refer to how often the element is repeated / evaluated when run. + * It may either refer to loop bounds or how often a recursion is repeated. If either or both values + * is/are set to `None`, it cannot be determined of often the element is actually repeated. + * Otherwise, the number of repetitions is computed by `upperBound - lowerBound`. */ -case class TreeLoopElement( - child: TreeElement, - numLoopIterations: Option[Int] -) extends TreeElement(ListBuffer(child)) +case class StringTreeRepetition( + var child: StringTreeElement, + lowerBound: Option[Int] = None, + upperBound: Option[Int] = None +) extends StringTreeElement(ListBuffer(child)) /** - * For modelling conditionals, such as if, if-else, if-elseif-else, switch, and also as parent - * element for possible array values, but no loops! Even though loops are conditionals as well, - * they are to be modelled using [[TreeLoopElement]] (as they capture further information).
+ * [[StringTreeConcat]] models the concatenation of multiple strings. For example, if it is known + * that a string is the concatenation of ''s_1'', ..., ''s_n'' (in that order), use a + * [[StringTreeConcat]] element where the first child / first element in the `children`list + * represents ''s_1'' and the last child / last element ''s_n''. + */ +case class StringTreeConcat( + override val children: ListBuffer[StringTreeElement] +) extends StringTreeElement(children) + +/** + * [[StringTreeOr]] models that a string (or part of a string) has one out of several possible + * values. For instance, if in an `if` block and its corresponding `else` block two values, ''s1'' + * and ''s2'' are appended to a [[StringBuffer]], `sb`, a [[StringTreeOr]] can be used to model that + * `sb` can contain either ''s1'' or ''s2'' (but not both at the same time!).
+ * + * In contrast to [[StringTreeCond]], [[StringTreeOr]] provides several possible values for + * a (sub) string. + */ +case class StringTreeOr( + override val children: ListBuffer[StringTreeElement] +) extends StringTreeElement(children) + +/** + * [[StringTreeCond]] is used to model that a string (or part of a string) is optional / may + * not always be present. For example, if an `if` block (and maybe a corresponding `else if` but NO + * `else`) appends to a [[StringBuilder]], a [[StringTreeCond]] is appropriate.
* - * `TreeConditionalElement`s are supposed to have either at lease one other - * `TreeConditionalElement`, `TreeLoopElement`, or a [[TreeValueElement]] as children . A tree with - * a `TreeConditionalElement` that has no children is regarded as an invalid tree in this sense! + * In contrast to [[StringTreeOr]], [[StringTreeCond]] provides a way to express that a (sub) + * string may have (contain) a particular but not necessarily. */ -case class TreeConditionalElement( - override val children: ListBuffer[TreeElement], -) extends TreeElement(children) +case class StringTreeCond( + override val children: ListBuffer[StringTreeElement] +) extends StringTreeElement(children) /** - * TreeExprElement are the only elements which are supposed to act as leafs within a + * [[StringTreeConst]]s are the only elements which are supposed to act as leafs within a * [[StringTree]]. - * They may have one `child` but do not need to have children necessarily. Intuitively, a - * TreeExprElement, ''e1'', which has a child ''e2'', represents the concatenation of ''e1'' and - * ''e2''.
* * `sci` is a [[StringConstancyInformation]] instance that resulted from evaluating an - * expression. + * expression and that represents part of the value(s) a string may have. */ -case class TreeValueElement( - var child: Option[TreeElement], - sci: StringConstancyInformation -) extends TreeElement( - child match { - case Some(c) ⇒ ListBuffer(c) - case None ⇒ ListBuffer() - } -) +case class StringTreeConst( + sci: StringConstancyInformation +) extends StringTreeElement(ListBuffer()) diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala index 8816b1f25f..a3053c27bf 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala @@ -7,6 +7,6 @@ package object properties { * `StringTree` is used to build trees that represent how a particular string looks and / or how * it can looks like from a pattern point of view (thus be approximated). */ - type StringTree = TreeElement + type StringTree = StringTreeElement } From f65dab46fae37a6a5e66e9d8d40c66bf1bb7e6a7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:35:10 +0100 Subject: [PATCH 016/316] The construction of string trees was changed in the way that the order of string definitions / modifications is now respected => the results of test cases is now precisely predictive --- .../string_definition/TestMethods.java | 18 +++++++++--------- .../LocalStringDefinitionAnalysis.scala | 6 +++--- .../expr_processing/ArrayLoadProcessor.scala | 11 +++++++---- .../expr_processing/ExprHandler.scala | 13 ++++++------- .../NewStringBuilderProcessor.scala | 12 ++++++------ .../properties/StringTree.scala | 6 +++++- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index d6c2857074..bbac013651 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -105,8 +105,8 @@ public void fromConstantAndFunctionCall() { @StringDefinitions( value = "array access with unknown index", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(java.lang.String | java.lang.System | " - + "java.lang.Runnable | java.lang.StringBuilder)" + expectedStrings = "(java.lang.String | java.lang.StringBuilder | " + + "java.lang.System | java.lang.Runnable)" ) public void fromStringArray(int index) { String[] classes = { @@ -137,7 +137,7 @@ public void multipleConstantDefSites(boolean cond) { value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(java.lang.\\w | \\w | java.lang.System | java.lang.Object)" + expectedStrings = "(java.lang.Object | \\w | java.lang.System | java.lang.\\w)" ) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -166,7 +166,7 @@ public void multipleDefSites(int value) { @StringDefinitions( value = "if-else control structure which append to a string builder with an int expr", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "([AnIntegerValue] | x)" + expectedStrings = "(x | [AnIntegerValue])" ) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb = new StringBuilder(); @@ -188,9 +188,9 @@ public void ifElseWithStringBuilderWithConstantInt() { StringBuilder sb = new StringBuilder(); int i = new Random().nextInt(); if (i % 2 == 0) { - sb.append("x"); - } else { sb.append(i); + } else { + sb.append("x"); } analyzeString(sb.toString()); } @@ -198,7 +198,7 @@ public void ifElseWithStringBuilderWithConstantInt() { @StringDefinitions( value = "if-else control structure which append to a string builder", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(b | a)" + expectedStrings = "(a | b)" ) public void ifElseWithStringBuilder1() { StringBuilder sb; @@ -214,7 +214,7 @@ public void ifElseWithStringBuilder1() { @StringDefinitions( value = "if-else control structure which append to a string builder", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(c | b)" + expectedStrings = "a(b | c)" ) public void ifElseWithStringBuilder2() { StringBuilder sb = new StringBuilder("a"); @@ -230,7 +230,7 @@ public void ifElseWithStringBuilder2() { @StringDefinitions( value = "if-else control structure which append to a string builder multiple times", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(yz | bc)" + expectedStrings = "a(bc | yz)" ) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 28a57c2e81..2856121a8e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -46,14 +46,14 @@ class LocalStringDefinitionAnalysis( val stmts = tacProvider(data._2).stmts val exprHandler = ExprHandler(p, data._2) - val defSites = data._1.definedBy + val defSites = data._1.definedBy.toArray.sorted if (ExprHandler.isStringBuilderToStringCall(stmts(defSites.head).asAssignment.expr)) { val subtrees = ArrayBuffer[StringTree]() defSites.foreach { nextDefSite ⇒ val treeElements = ExprHandler.getDefSitesOfToStringReceiver( stmts(nextDefSite).asAssignment.expr - ).map { exprHandler.processDefSite _ }.filter(_.isDefined).map { _.get } - if (treeElements.size == 1) { + ).map { exprHandler.processDefSite }.filter(_.isDefined).map { _.get } + if (treeElements.length == 1) { subtrees.append(treeElements.head) } else { subtrees.append(StringTreeCond(treeElements.to[ListBuffer])) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index 0e9b6187a1..ab43261cef 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -60,13 +60,16 @@ class ArrayLoadProcessor( case al: ArrayLoad[V] ⇒ val children = ListBuffer[StringTreeElement]() // Loop over all possible array values - al.arrayRef.asVar.definedBy.filter(!ignore.contains(_)).foreach { next ⇒ + val sortedAlDefs = al.arrayRef.asVar.definedBy.toArray.sorted + sortedAlDefs.filter(!ignore.contains(_)).foreach { next ⇒ val arrDecl = stmts(next) - arrDecl.asAssignment.targetVar.usedBy.filter { + val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + sortedArrDeclUses.filter { stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ - val arrValues = stmts(f).asArrayStore.value.asVar.definedBy.map { - exprHandler.processDefSite _ + val sortedSDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted + val arrValues = sortedSDefs.map { + exprHandler.processDefSite }.filter(_.isDefined).map(_.get) children.appendAll(arrValues) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index 09fcb7d0cd..cea4d67ecf 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -4,7 +4,6 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.Method import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG -import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree @@ -111,7 +110,7 @@ class ExprHandler(p: SomeProject, m: Method) { case 0 ⇒ None case 1 ⇒ processDefSite(defSites.head) case _ ⇒ - val processedSites = defSites.filter(_ >= 0).map(processDefSite _) + val processedSites = defSites.filter(_ >= 0).toArray.sorted.map(processDefSite) Some(StringTreeOr( processedSites.filter(_.isDefined).map(_.get).to[ListBuffer] )) @@ -131,7 +130,7 @@ class ExprHandler(p: SomeProject, m: Method) { return None } - val children = defSites.map(processDefSite).filter(_.isDefined).map(_.get) + val children = defSites.sorted.map(processDefSite).filter(_.isDefined).map(_.get) if (children.isEmpty) { None } else if (children.size == 1) { @@ -262,15 +261,15 @@ object ExprHandler { * Retrieves the definition sites of the receiver of a [[StringBuilder.toString]] call. * * @param expr The expression that contains the receiver whose definition sites to get. - * @return If `expr` does not conform to the expected structure, an [[EmptyIntTrieSet]] is + * @return If `expr` does not conform to the expected structure, an empty array is * returned (avoid by using [[isStringBuilderToStringCall]]) and otherwise the * definition sites of the receiver. */ - def getDefSitesOfToStringReceiver(expr: Expr[V]): IntTrieSet = + def getDefSitesOfToStringReceiver(expr: Expr[V]): Array[Int] = if (!isStringBuilderToStringCall(expr)) { - EmptyIntTrieSet + Array() } else { - expr.asVirtualFunctionCall.receiver.asVar.definedBy + expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray.sorted } /** diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index f426ccf392..37a128cef8 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -4,7 +4,6 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG import org.opalj.collection.immutable.EmptyIntTrieSet -import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.Stmt import org.opalj.fpcf.string_definition.properties.StringTree @@ -41,12 +40,12 @@ class NewStringBuilderProcessor( ): Option[StringTree] = { assignment.expr match { case _: New ⇒ - val useSites = assignment.targetVar.usedBy.filter(!ignore.contains(_)) - val (inits, nonInits) = getInitsAndNonInits(useSites, stmts, cfg) + val uses = assignment.targetVar.usedBy.filter(!ignore.contains(_)).toArray.sorted + val (inits, nonInits) = getInitsAndNonInits(uses, stmts, cfg) val initTreeNodes = ListBuffer[StringTree]() val nonInitTreeNodes = ListBuffer[StringTree]() - inits.sorted.foreach { next ⇒ + inits.foreach { next ⇒ val toProcess = stmts(next) match { case init: NonVirtualMethodCall[V] if init.params.nonEmpty ⇒ init.params.head.asVar.definedBy @@ -132,7 +131,7 @@ class NewStringBuilderProcessor( * @return */ private def getInitsAndNonInits( - useSites: IntTrieSet, stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]] + useSites: Array[Int], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]] ): (List[Int], List[List[Int]]) = { val inits = ListBuffer[Int]() var nonInits = ListBuffer[Int]() @@ -164,7 +163,8 @@ class NewStringBuilderProcessor( } // Sort the lists in ascending order as this is more intuitive - (inits.toList, blocks.map(_._2.toList.sorted).toList) + val reversedBlocks = mutable.LinkedHashMap(blocks.toSeq.reverse: _*) + (inits.toList.sorted, reversedBlocks.map(_._2.toList.sorted).toList) } } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 748fd50086..f8ac7bfaf6 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -101,7 +101,11 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme cs.foreach { case nextC @ StringTreeOr(subChildren) ⇒ simplifyAcc(nextC) - subChildren.foreach(subtree.children.append(_)) + var insertIndex = subtree.children.indexOf(nextC) + subChildren.foreach { next ⇒ + subtree.children.insert(insertIndex, next) + insertIndex += 1 + } subtree.children.-=(nextC) case _ ⇒ } From 798686018ce8213c8664018ac744d0dd5b0ae669 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:37:04 +0100 Subject: [PATCH 017/316] Started working on the case when several consecutive 'append' calls are made. To finish this, a dominator tree is necessary. --- .../LocalStringDefinitionAnalysis.scala | 2 +- .../expr_processing/ArrayLoadProcessor.scala | 3 +- .../expr_processing/ExprHandler.scala | 11 +-- .../NewStringBuilderProcessor.scala | 23 +++--- .../VirtualFunctionCallProcessor.scala | 77 ++++++++++++++----- 5 files changed, 75 insertions(+), 41 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 2856121a8e..f91e205999 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -66,7 +66,7 @@ class LocalStringDefinitionAnalysis( } // If not a call to StringBuilder.toString, then we deal with pure strings else { Result(data, StringConstancyProperty( - exprHandler.processDefSites(data._1.definedBy).get + exprHandler.processDefSites(data._1.definedBy.toArray).get )) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala index ab43261cef..5ae91e9552 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala @@ -60,8 +60,7 @@ class ArrayLoadProcessor( case al: ArrayLoad[V] ⇒ val children = ListBuffer[StringTreeElement]() // Loop over all possible array values - val sortedAlDefs = al.arrayRef.asVar.definedBy.toArray.sorted - sortedAlDefs.filter(!ignore.contains(_)).foreach { next ⇒ + al.arrayRef.asVar.definedBy.toArray.sorted.foreach { next ⇒ val arrDecl = stmts(next) val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted sortedArrDeclUses.filter { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index cea4d67ecf..bf629fc2ed 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -4,7 +4,6 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.Method import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG -import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.StringTreeConcat @@ -63,9 +62,7 @@ class ExprHandler(p: SomeProject, m: Method) { val expr = ctxStmts(defSite) match { case a: Assignment[V] ⇒ a.expr case e: ExprStmt[V] ⇒ e.expr - case _ ⇒ throw new IllegalArgumentException( - s"cannot process ${ctxStmts(defSite)}" - ) + case _ ⇒ return None } val exprProcessor: AbstractExprProcessor = expr match { case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor(this) @@ -105,12 +102,12 @@ class ExprHandler(p: SomeProject, m: Method) { * control flow statements; thus, this function returns a tree with a * [[StringTreeOr]] as root and each definition site as a child. */ - def processDefSites(defSites: IntTrieSet): Option[StringTree] = - defSites.size match { + def processDefSites(defSites: Array[Int]): Option[StringTree] = + defSites.length match { case 0 ⇒ None case 1 ⇒ processDefSite(defSites.head) case _ ⇒ - val processedSites = defSites.filter(_ >= 0).toArray.sorted.map(processDefSite) + val processedSites = defSites.filter(_ >= 0).sorted.map(processDefSite) Some(StringTreeOr( processedSites.filter(_.isDefined).map(_.get).to[ListBuffer] )) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 37a128cef8..9daa71461e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -3,7 +3,6 @@ package org.opalj.fpcf.analyses.string_definition.expr_processing import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG -import org.opalj.collection.immutable.EmptyIntTrieSet import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.Stmt import org.opalj.fpcf.string_definition.properties.StringTree @@ -48,24 +47,25 @@ class NewStringBuilderProcessor( inits.foreach { next ⇒ val toProcess = stmts(next) match { case init: NonVirtualMethodCall[V] if init.params.nonEmpty ⇒ - init.params.head.asVar.definedBy + init.params.head.asVar.definedBy.toArray.sorted case assignment: Assignment[V] ⇒ val vfc = assignment.expr.asVirtualFunctionCall var defs = vfc.receiver.asVar.definedBy if (vfc.params.nonEmpty) { vfc.params.head.asVar.definedBy.foreach(defs += _) } - defs + defs ++= assignment.targetVar.asVar.usedBy + defs.toArray.sorted case _ ⇒ - EmptyIntTrieSet + Array() } - val processed = if (toProcess.size == 1) { + val processed = if (toProcess.length == 1) { val intermRes = exprHandler.processDefSite(toProcess.head) if (intermRes.isDefined) intermRes else None } else { - val children = toProcess.map(exprHandler.processDefSite _). + val children = toProcess.map(exprHandler.processDefSite). filter(_.isDefined).map(_.get) - children.size match { + children.length match { case 0 ⇒ None case 1 ⇒ Some(children.head) case _ ⇒ Some(StringTreeConcat(children.to[ListBuffer])) @@ -125,7 +125,8 @@ class NewStringBuilderProcessor( /** * - * @param useSites Not-supposed to contain already processed sites. + * @param useSites Not-supposed to contain already processed sites. Also, they should be in + * ascending order. * @param stmts A list of statements (the one that was passed on to the `process`function of * this class). * @return @@ -140,8 +141,10 @@ class NewStringBuilderProcessor( // Constructors are identified by the "init" method and assignments (ExprStmts, in // contrast, point to non-constructor related calls) case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ inits.append(next) - case _: Assignment[V] ⇒ inits.append(next) - case _ ⇒ nonInits.append(next) + case _: Assignment[V] ⇒ + // TODO: Use dominator tree to determine whether init or noninit + inits.append(next) + case _ ⇒ nonInits.append(next) } } // Sort in descending order to enable correct grouping in the next step diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala index afe953a1c0..0d71706258 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala @@ -45,29 +45,32 @@ class VirtualFunctionCallProcessor( override def processAssignment( assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(assignment.expr, stmts, ignore) + ): Option[StringTree] = process(assignment.expr, Some(assignment), stmts, ignore) /** - * This implementation does not change / implement the behavior of - * [[AbstractExprProcessor.processExpr]]. + * @see [[AbstractExprProcessor.processExpr]]. + * + * @note For expressions, some information are not available that an [[Assignment]] captures. + * Nonetheless, as much information as possible is extracted from this implementation (but + * no use sites for `append` calls, for example). */ override def processExpr( expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(expr, stmts, ignore) + ): Option[StringTree] = process(expr, None, stmts, ignore) /** - * Wrapper function for processing expressions. + * Wrapper function for processing assignments. */ private def process( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] + expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { expr match { case vfc: VirtualFunctionCall[V] ⇒ if (ExprHandler.isStringBuilderAppendCall(expr)) { - Some(processAppendCall(vfc, stmts, ignore)) + processAppendCall(expr, assignment, stmts, ignore) } else if (ExprHandler.isStringBuilderToStringCall(expr)) { - processToStringCall(vfc, stmts, ignore) + processToStringCall(assignment, stmts, ignore) } // A call to method which is not (yet) supported else { val ps = ExprHandler.classNameToPossibleString( @@ -83,25 +86,52 @@ class VirtualFunctionCallProcessor( * Function for processing calls to [[StringBuilder#append]]. */ private def processAppendCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): StringTreeElement = { - val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) - val appendValue = valueOfAppendCall(call, stmts) - val siblings = exprHandler.processDefSites(defSites) - if (siblings.isDefined) { - StringTreeConcat(ListBuffer[StringTreeElement](siblings.get, appendValue)) + expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTreeElement] = { + val defSites = expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray.sorted + val appendValue = valueOfAppendCall(expr.asVirtualFunctionCall, stmts, ignore) + // Append has been processed before => do not compute again + if (appendValue.isEmpty) { + return None + } + + val leftSiblings = exprHandler.processDefSites(defSites) + // For assignments, we can take use sites into consideration as well + var rightSiblings: Option[StringTree] = None + if (assignment.isDefined) { + val useSites = assignment.get.targetVar.asVar.usedBy.toArray.sorted + rightSiblings = exprHandler.processDefSites(useSites) + } + + if (leftSiblings.isDefined || rightSiblings.isDefined) { + // Combine siblings and return + val concatElements = ListBuffer[StringTreeElement]() + if (leftSiblings.isDefined) { + concatElements.append(leftSiblings.get) + } + concatElements.append(appendValue.get) + if (rightSiblings.isDefined) { + concatElements.append(rightSiblings.get) + } + Some(StringTreeConcat(concatElements)) } else { - appendValue + Some(appendValue.get) } } /** - * Function for processing calls to [[StringBuilder.toString]]. + * Function for processing calls to [[StringBuilder.toString]]. Note that a value not equals to + * `None` can only be expected if `assignments` is defined. */ private def processToStringCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] + assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { + if (assignment.isEmpty) { + return None + } + val children = ListBuffer[StringTreeElement]() + val call = assignment.get.expr.asVirtualFunctionCall val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) defSites.foreach { exprHandler.processDefSite(_) match { @@ -132,9 +162,14 @@ class VirtualFunctionCallProcessor( * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC]]. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]] - ): StringTreeConst = { + call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTreeConst] = { val defAssignment = call.params.head.asVar.definedBy.head + // The definition has been seen before => do not recompute + if (ignore.contains(defAssignment)) { + return None + } + val assign = stmts(defAssignment).asAssignment val sci = assign.expr match { case _: NonVirtualFunctionCall[V] ⇒ @@ -150,7 +185,7 @@ class VirtualFunctionCallProcessor( ) StringConstancyInformation(DYNAMIC, possibleString) } - StringTreeConst(sci) + Some(StringTreeConst(sci)) } } From 3f484a5e01197bb55d1d0a3402f34189eca9570e Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:39:46 +0100 Subject: [PATCH 018/316] By making use of the dominator tree, the test case 'directAppendConcats' works now. --- .../string_definition/TestMethods.java | 37 +++++++++++++------ .../NewStringBuilderProcessor.scala | 34 ++++++++++++++++- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index bbac013651..c856e67927 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -23,18 +23,6 @@ public class TestMethods { public void analyzeString(String s) { } - // The following is a strange case (difficult / impossible? to follow back information flow) - // @StringDefinitions( - // value = "checks if a string value with > 1 continuous appends is determined correctly", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "java.lang.String" - // ) - // public void directAppendConcat() { - // StringBuilder sb = new StringBuilder("java"); - // sb.append(".").append("lang").append(".").append("String"); - // analyzeString(sb.toString()); - // } - @StringDefinitions( value = "read-only string, trivial case", expectedLevel = StringConstancyLevel.CONSTANT, @@ -80,6 +68,17 @@ public void advStringConcat() { analyzeString(className); } + @StringDefinitions( + value = "checks if a string value with > 2 continuous appends is determined correctly", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "java.lang.String" + ) + public void directAppendConcats() { + StringBuilder sb = new StringBuilder("java"); + sb.append(".").append("lang").append(".").append("String"); + analyzeString(sb.toString()); + } + @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", expectedLevel = StringConstancyLevel.DYNAMIC, @@ -274,6 +273,20 @@ public void simpleForLoopWithUnknownBounds() { analyzeString(sb.toString()); } + // @StringDefinitions( + // value = "if-else control structure which append to a string builder multiple times", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(b)+" + // ) + // public void ifElseWithStringBuilder3() { + // StringBuilder sb = new StringBuilder("a"); + // int i = new Random().nextInt(); + // if (i % 2 == 0) { + // sb.append("b"); + // } + // analyzeString(sb.toString()); + // } + // @StringDefinitions( // value = "if-else control structure within a for loop with known loop bounds", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 9daa71461e..c5ea59a39d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -10,6 +10,7 @@ import org.opalj.fpcf.string_definition.properties.StringTreeConcat import org.opalj.fpcf.string_definition.properties.StringTreeConst import org.opalj.fpcf.string_definition.properties.StringTreeOr import org.opalj.fpcf.string_definition.properties.StringTreeRepetition +import org.opalj.graphs.DominatorTree import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.New @@ -134,16 +135,22 @@ class NewStringBuilderProcessor( private def getInitsAndNonInits( useSites: Array[Int], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]] ): (List[Int], List[List[Int]]) = { + val domTree = cfg.dominatorTree val inits = ListBuffer[Int]() var nonInits = ListBuffer[Int]() + useSites.foreach { next ⇒ stmts(next) match { // Constructors are identified by the "init" method and assignments (ExprStmts, in // contrast, point to non-constructor related calls) case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ inits.append(next) case _: Assignment[V] ⇒ - // TODO: Use dominator tree to determine whether init or noninit - inits.append(next) + // Use dominator tree to determine whether init or noninit + if (doesDominate(inits.toArray, next, domTree)) { + nonInits.append(next) + } else { + inits.append(next) + } case _ ⇒ nonInits.append(next) } } @@ -170,4 +177,27 @@ class NewStringBuilderProcessor( (inits.toList.sorted, reversedBlocks.map(_._2.toList.sorted).toList) } + /** + * `doesDominate` checks if a list of `possibleDominators` dominates another statement, + * `toCheck`, by using the given dominator tree, `domTree`. If so, true is returned and false + * otherwise. + */ + private def doesDominate( + possibleDominators: Array[Int], toCheck: Int, domTree: DominatorTree + ): Boolean = { + var nextToCheck = toCheck + var pd = possibleDominators.filter(_ < nextToCheck) + + while (pd.nonEmpty) { + if (possibleDominators.contains(nextToCheck)) { + return true + } + + nextToCheck = domTree.dom(nextToCheck) + pd = pd.filter(_ <= nextToCheck) + } + + false + } + } From 7530f683a1ec439cf529520a3e3352b95cb520a1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 8 Nov 2018 19:40:18 +0100 Subject: [PATCH 019/316] Extended the string definition analysis which now supports the extended test case in TestMethods.java. --- .../fixtures/string_definition/TestMethods.java | 5 +++-- .../NewStringBuilderProcessor.scala | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index c856e67927..86e8595538 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -229,16 +229,17 @@ public void ifElseWithStringBuilder2() { @StringDefinitions( value = "if-else control structure which append to a string builder multiple times", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(bc | yz)" + expectedStrings = "a(bcd | xyz)" ) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); if (i % 2 == 0) { - // TODO: Extend the case with three append calls per block sb.append("b"); sb.append("c"); + sb.append("d"); } else { + sb.append("x"); sb.append("y"); sb.append("z"); } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index c5ea59a39d..68d0c5fe3f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -157,7 +157,12 @@ class NewStringBuilderProcessor( // Sort in descending order to enable correct grouping in the next step nonInits = nonInits.sorted.reverse - // Next, group all non inits into lists depending on their basic block in the CFG + // Next, group all non inits into lists depending on their basic block in the CFG; as the + // "belongs to parent" relationship is transitive, there are two approaches: 1) recursively + // check or 2) store grandchildren in a flat structure as well. Here, 2) is implemented as + // only references are stored which is not so expensive. However, this leads to the fact + // that we need to create a distinct map before returning (see declaration of uniqueLists + // below) val blocks = mutable.LinkedHashMap[BasicBlock, ListBuffer[Int]]() nonInits.foreach { next ⇒ val nextBlock = cfg.bb(next) @@ -166,15 +171,17 @@ class NewStringBuilderProcessor( case _ ⇒ false } if (parentBlock.nonEmpty) { - blocks(parentBlock.head.asBasicBlock).append(next) + val list = blocks(parentBlock.head.asBasicBlock) + list.append(next) + blocks += (nextBlock → list) } else { blocks += (nextBlock → ListBuffer[Int](next)) } } - // Sort the lists in ascending order as this is more intuitive - val reversedBlocks = mutable.LinkedHashMap(blocks.toSeq.reverse: _*) - (inits.toList.sorted, reversedBlocks.map(_._2.toList.sorted).toList) + // Make the list unique (as described above) and sort it in ascending order + val uniqueLists = blocks.values.toList.distinct.reverse + (inits.toList.sorted, uniqueLists.map(_.toList)) } /** From 20295112855d3de1e71f0a956d5866ea044c5ddd Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 9 Nov 2018 14:09:32 +0100 Subject: [PATCH 020/316] Moved the 'doesDominate' method into the DominatorTree.scala file. --- .../NewStringBuilderProcessor.scala | 26 +----------- .../org/opalj/graphs/DominatorTree.scala | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 68d0c5fe3f..35b7a9d6bc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -10,7 +10,6 @@ import org.opalj.fpcf.string_definition.properties.StringTreeConcat import org.opalj.fpcf.string_definition.properties.StringTreeConst import org.opalj.fpcf.string_definition.properties.StringTreeOr import org.opalj.fpcf.string_definition.properties.StringTreeRepetition -import org.opalj.graphs.DominatorTree import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.New @@ -146,7 +145,7 @@ class NewStringBuilderProcessor( case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ inits.append(next) case _: Assignment[V] ⇒ // Use dominator tree to determine whether init or noninit - if (doesDominate(inits.toArray, next, domTree)) { + if (domTree.doesDominate(inits.toArray, next)) { nonInits.append(next) } else { inits.append(next) @@ -184,27 +183,4 @@ class NewStringBuilderProcessor( (inits.toList.sorted, uniqueLists.map(_.toList)) } - /** - * `doesDominate` checks if a list of `possibleDominators` dominates another statement, - * `toCheck`, by using the given dominator tree, `domTree`. If so, true is returned and false - * otherwise. - */ - private def doesDominate( - possibleDominators: Array[Int], toCheck: Int, domTree: DominatorTree - ): Boolean = { - var nextToCheck = toCheck - var pd = possibleDominators.filter(_ < nextToCheck) - - while (pd.nonEmpty) { - if (possibleDominators.contains(nextToCheck)) { - return true - } - - nextToCheck = domTree.dom(nextToCheck) - pd = pd.filter(_ <= nextToCheck) - } - - false - } - } diff --git a/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala b/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala index e2532d912e..e5d561ff2d 100644 --- a/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala +++ b/OPAL/common/src/main/scala/org/opalj/graphs/DominatorTree.scala @@ -26,6 +26,47 @@ final class DominatorTree private ( def isAugmented: Boolean = hasVirtualStartNode + /** + * Checks whether a given node is dominated by another node in this dominator tree. + * + * @param possibleDominator The index of the node which could be a dominator. + * @param toCheck The index of the node which is to be checked whether it is dominated by the + * node identified by `possibleDominator`. + * @return Returns `true` if the node identified by `toCheck` is dominated by the node + * identified by `possibleDominator`. Otherwise, false will be returned. + */ + def doesDominate( + possibleDominator: Int, toCheck: Int + ): Boolean = doesDominate(Array(possibleDominator), toCheck) + + /** + * Convenient function which checks whether at least one node of a list, `possibleDominators`, + * dominates another node, `toCheck`. Note that analogously to `doesDominate(Int, Int)`, + * `possibleDominators` and `toCheck` contain the indices of the nodes. + * + * @note One could easily simulate the behavior of this function by looping over the possible + * dominators and call `doesDominate(Int, Int)` for each. However, this function has the + * advantage that only one iteration is necessary instead of ''n'' where ''n'' is the + * number of possible dominators. + */ + def doesDominate( + possibleDominators: Array[Int], toCheck: Int + ): Boolean = { + var nextToCheck = toCheck + var pd = possibleDominators.filter(_ < nextToCheck) + + while (pd.nonEmpty) { + if (possibleDominators.contains(nextToCheck)) { + return true + } + + nextToCheck = dom(nextToCheck) + pd = pd.filter(_ <= nextToCheck) + } + + false + } + } /** From 8334047e04dd9784329435cc3b2f0cf8a212c2a8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 9 Nov 2018 21:24:00 +0100 Subject: [PATCH 021/316] Implemented an algorithm for finding natural loops that makes use of dominator information (and is not as "primitive" as the previous one). --- .../expr_processing/ExprHandler.scala | 73 +------------------ .../src/main/scala/org/opalj/br/cfg/CFG.scala | 51 +++++++++++++ 2 files changed, 54 insertions(+), 70 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index bf629fc2ed..e8e4f0af39 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -13,8 +13,6 @@ import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.ExprStmt -import org.opalj.tac.Goto -import org.opalj.tac.If import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.SimpleTACAIKey @@ -151,59 +149,6 @@ object ExprHandler { */ def apply(p: SomeProject, m: Method): ExprHandler = new ExprHandler(p, m) - /** - * Determines the successor [[Goto]] element for the given definition site, if present. - * - * @param defSite The definition site to check. - * @param cfg The control flow graph which is required for that operation. - * @return Either returns the corresponding [[Goto]] element or `None` in case there is non - * following the given site. - */ - private def getSuccessorGoto(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Option[Goto] = { - val successorBlocks = cfg.bb(defSite).successors.filter(_.isBasicBlock) - val successorDefSites = ListBuffer[Int]() - var goto: Option[Goto] = None - - successorBlocks.foreach { next ⇒ - for (i ← next.asBasicBlock.startPC to next.asBasicBlock.endPC) { - cfg.code.instructions(i) match { - case gt: Goto ⇒ goto = Some(gt) - case _ ⇒ if (i > defSite) successorDefSites.append(i) - } - } - } - if (goto.isDefined) { - goto - } else { - val successor = successorDefSites.map(getSuccessorGoto(_, cfg)).filter(_.isDefined) - if (successor.nonEmpty && successor.head.isDefined) { - successor.head - } else { - None - } - } - } - - /** - * Determines the if statement that belongs to the given `goto`. - * - * @param goto The [[Goto]] for which to determine the corresponding [[If]]. Note that the - * `goto` is not required to have a corresponding [[If]]. - * @param cfg The control flow graph which is required for that operation. - * @return Either returns the corresponding [[If]] or `None` if there is no such [[If]]. - */ - private def getIfOfGoto(goto: Goto, cfg: CFG[Stmt[V], TACStmts[V]]): Option[If[V]] = { - cfg.code.instructions(goto.targetStmt) match { - case i: If[V] ⇒ Some(i) - case a: Assignment[V] ⇒ - val possibleIfsSites = a.targetVar.usedBy - possibleIfsSites.filter(cfg.code.instructions(_).isInstanceOf[If[V]]).map { - cfg.code.instructions(_).asIf - }.headOption - case _ ⇒ None - } - } - /** * Checks whether the given definition site is within a loop. * @@ -211,22 +156,10 @@ object ExprHandler { * @param cfg The control flow graph which is required for that operation. * @return Returns `true` if the given site resides within a loop and `false` otherwise. */ - def isWithinLoop(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { - val successorGoto = getSuccessorGoto(defSite, cfg) - if (successorGoto.isEmpty) { - false - } else { - val correspondingIf = getIfOfGoto(successorGoto.get, cfg) - if (correspondingIf.isEmpty) { - false - } else { - // To be within a loop, the definition site must be within the if and goto - val posIf = cfg.code.instructions.indexOf(correspondingIf.get) - val posGoto = cfg.code.instructions.indexOf(successorGoto.get) - defSite > posIf && defSite < posGoto - } + def isWithinLoop(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = + cfg.findNaturalLoops().foldLeft(false) { (previous: Boolean, nextLoop: List[Int]) ⇒ + previous || nextLoop.contains(defSite) } - } /** * Checks whether an expression contains a call to [[StringBuilder.toString]]. diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 753030520c..2872fb960c 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -21,6 +21,9 @@ import org.opalj.graphs.DefaultMutableNode import org.opalj.graphs.DominatorTree import org.opalj.graphs.Node +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + /** * Represents the control flow graph of a method. * @@ -737,6 +740,54 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( ) } + // We use this variable for caching, as the loop information of a CFG are permanent and do not + // need to be recomputed (see findNaturalLoops for usage) + private var naturalLoops: Option[List[List[Int]]] = None + + /** + * ''findNaturalLoops'' finds all natural loops in this dominator tree and returns them as a + * list of lists where each inner list represents one loop and the Int values correspond to the + * indices of the nodes. + * + * @return Returns all found loops. The structure of the inner lists is as follows: The first + * element of each inner list, i.e., each loop, is the loop header and the very last + * element is the one that has a back-edge to the loop header. In between, elements are + * ordered according to their occurrences, i.e., if ''n1'' is executed before ''n2'', + * the index of ''n1'' is less than the index of ''n2''. + * @note This function only focuses on natural loops, i.e., it may / will produce incorrect + * results on irreducible loops. For further information, see + * [[http://www.cs.princeton.edu/courses/archive/spring03/cs320/notes/loops.pdf]]. + */ + def findNaturalLoops(): List[List[Int]] = { + // Find loops only if that has not been done before + if (naturalLoops.isEmpty) { + val domTree = dominatorTree + // Execute a depth-first-search to find all back-edges + val start = startBlock.startPC + val seenNodes = ListBuffer[Int](start) + val toVisitStack = mutable.Stack[Int](successors(start).toArray: _*) + // backedges stores all back-edges in the form (from, to) (where to dominates from) + val backedges = ListBuffer[(Int, Int)]() + while (toVisitStack.nonEmpty) { + val from = toVisitStack.pop() + val to = successors(from).toArray + // Check for back-edges + to.filter { next ⇒ + val index = seenNodes.indexOf(next) + index > -1 && domTree.doesDominate(seenNodes(index), from) + }.foreach(destIndex ⇒ backedges.append((from, destIndex))) + + seenNodes.append(from) + toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) + } + + // Finally, assemble the lists of loop elements + naturalLoops = Some(backedges.map { case (dest, root) ⇒ root.to(dest).toList }.toList) + } + + naturalLoops.get + } + // --------------------------------------------------------------------------------------------- // // Visualization & Debugging From 97b152f0963e807003e71c137de1171b83bcea89 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 10 Nov 2018 16:48:05 +0100 Subject: [PATCH 022/316] Minor changes on the LocalStringDefinitionMatcher and the related file StringConstancyProperty. --- .../LocalStringDefinitionMatcher.scala | 26 +++---------------- .../properties/StringConstancyProperty.scala | 4 +-- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 1efb62f7f7..8424a33ae6 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -44,20 +44,6 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { } } - /** - * Takes an [[AnnotationLike]] which represents a [[org.opalj.fpcf.properties.StringConstancyProperty]] and returns its - * stringified representation. - * - * @param a The annotation. This function requires that it holds a StringConstancyProperty. - * @return The stringified representation, which is identical to - * [[org.opalj.fpcf.properties.StringConstancyProperty.toString]]. - */ - private def propertyAnnotation2Str(a: AnnotationLike): String = { - val constancyLevel = getConstancyLevel(a).get.toLowerCase - val ps = getExpectedStrings(a).get - s"StringConstancyProperty { Constancy Level: $constancyLevel; Possible Strings: $ps }" - } - /** * @inheritdoc */ @@ -71,18 +57,14 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { val prop = properties.filter( _.isInstanceOf[StringConstancyProperty] ).head.asInstanceOf[StringConstancyProperty] - val reducedProp = prop.stringTree.simplify().reduce() + val reducedProp = prop.stringTree.simplify().groupRepetitionElements().reduce() val expLevel = getConstancyLevel(a).get val actLevel = reducedProp.constancyLevel.toString - if (expLevel.toLowerCase != actLevel.toLowerCase) { - return Some(propertyAnnotation2Str(a)) - } - - // TODO: This string comparison is not very robust val expStrings = getExpectedStrings(a).get - if (expStrings != reducedProp.possibleStrings) { - return Some(propertyAnnotation2Str(a)) + val actStrings = reducedProp.possibleStrings + if ((expLevel.toLowerCase != actLevel.toLowerCase) || (expStrings != actStrings)) { + return Some(reducedProp.toString) } None diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index 274c28b4e7..e3682da87e 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -24,9 +24,7 @@ class StringConstancyProperty( final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - val sci = stringTree.reduce() - s"StringConstancyProperty { Constancy Level: ${sci.constancyLevel}; "+ - s"Possible Strings: ${sci.possibleStrings} }" + stringTree.reduce().toString } } From 4011799ad743730367ad69589047ac6c1a193d73 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 10 Nov 2018 16:49:25 +0100 Subject: [PATCH 023/316] Extended the string definition analysis in a way that it can now recognize an if-else block within a for loop correctly (see added test case). --- .../string_definition/TestMethods.java | 35 ++++---- .../expr_processing/ExprHandler.scala | 3 +- .../properties/StringTree.scala | 80 +++++++++++++++++-- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 86e8595538..d0d3e9fc94 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -274,6 +274,24 @@ public void simpleForLoopWithUnknownBounds() { analyzeString(sb.toString()); } + @StringDefinitions( + value = "if-else control structure within a for loop with known loop bounds", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "((x | [AnIntegerValue]))*" + ) + public void ifElseInLoopWithKnownBounds() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 20; i++) { + if (i % 2 == 0) { + sb.append("x"); + } else { + sb.append(i + 1); + } + } + + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "if-else control structure which append to a string builder multiple times", // expectedLevel = StringConstancyLevel.CONSTANT, @@ -286,24 +304,7 @@ public void simpleForLoopWithUnknownBounds() { // sb.append("b"); // } // analyzeString(sb.toString()); - // } - // @StringDefinitions( - // value = "if-else control structure within a for loop with known loop bounds", - // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // expectedValues = { "(\"x\" | [Int Value])^20" } - // ) - // public void ifElseInLoopWithKnownBounds() { - // StringBuilder sb = new StringBuilder(); - // for (int i = 0; i < 20; i++) { - // if (i % 2 == 0) { - // sb.append("x"); - // } else { - // sb.append(i + 1); - // } - // } - // - // analyzeString(sb.toString()); // } private String getRuntimeClassName() { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala index e8e4f0af39..e2488a04bb 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala @@ -141,7 +141,8 @@ object ExprHandler { private val classNameMap = Map( "AnIntegerValue" → "[AnIntegerValue]", - "int" → "[AnIntegerValue]" + "int" → "[AnIntegerValue]", + "IntegerRange" → "[AnIntegerValue]", ) /** diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index f8ac7bfaf6..3631888be6 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -117,6 +117,58 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme } } + /** + * Accumulator function for grouping repetition elements. + */ + private def groupRepetitionElementsAcc(subtree: StringTree): StringTree = { + /** + * Function for processing [[StringTreeOr]] or [[StringTreeConcat]] elements as these cases + * are equal (except for distinguishing the object to return). Thus, make sure that only + * instance of these classes are passed. Otherwise, an exception will be thrown! + */ + def processConcatOrOrCase(subtree: StringTree): StringTree = { + if (!subtree.isInstanceOf[StringTreeOr] && !subtree.isInstanceOf[StringTreeConcat]) { + throw new IllegalArgumentException( + "can only process instances of StringTreeOr and StringTreeConcat" + ) + } + + var newChildren = subtree.children.map(groupRepetitionElementsAcc) + val repetitionElements = newChildren.filter(_.isInstanceOf[StringTreeRepetition]) + // Nothing to do when less than two repetition elements + if (repetitionElements.length <= 1) { + subtree + } else { + val childrenOfReps = repetitionElements.map( + _.asInstanceOf[StringTreeRepetition].child + ) + val newRepElement = StringTreeRepetition(StringTreeOr(childrenOfReps)) + val indexFirstChild = newChildren.indexOf(repetitionElements.head) + newChildren = newChildren.filterNot(_.isInstanceOf[StringTreeRepetition]) + newChildren.insert(indexFirstChild, newRepElement) + if (newChildren.length == 1) { + newChildren.head + } else { + if (subtree.isInstanceOf[StringTreeOr]) { + StringTreeOr(newChildren) + } else { + StringTreeConcat(newChildren) + } + } + } + } + + subtree match { + case sto: StringTreeOr ⇒ processConcatOrOrCase(sto) + case stc: StringTreeConcat ⇒ processConcatOrOrCase(stc) + case StringTreeCond(cs) ⇒ + StringTreeCond(cs.map(groupRepetitionElementsAcc)) + case StringTreeRepetition(child, _, _) ⇒ + StringTreeRepetition(groupRepetitionElementsAcc(child)) + case stc: StringTreeConst ⇒ stc + } + } + /** * Reduces this [[StringTree]] instance to a [[StringConstancyInformation]] object that captures * the information stored in this tree. @@ -140,6 +192,20 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme */ def simplify(): StringTree = simplifyAcc(this) + /** + * This function groups repetition elements that belong together. For example, an if-else block, + * which both append to a StringBuilder is modeled as a [[StringTreeOr]] with two + * [[StringTreeRepetition]] elements. Conceptually, this is not wrong, however, may create + * confusion when interpreting the tree / expression. This function finds such groupable + * children and actually groups them. + * + * @return This function modifies `this` tree and returns this instance, e.g., for chaining + * commands. + * @note Applying this function changes the representation of the tree but not produce a + * semantically different tree! + */ + def groupRepetitionElements(): StringTree = groupRepetitionElementsAcc(this) + /** * @return Returns all leaf elements of this instance. */ @@ -172,9 +238,9 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * Otherwise, the number of repetitions is computed by `upperBound - lowerBound`. */ case class StringTreeRepetition( - var child: StringTreeElement, - lowerBound: Option[Int] = None, - upperBound: Option[Int] = None + var child: StringTreeElement, + lowerBound: Option[Int] = None, + upperBound: Option[Int] = None ) extends StringTreeElement(ListBuffer(child)) /** @@ -184,7 +250,7 @@ case class StringTreeRepetition( * represents ''s_1'' and the last child / last element ''s_n''. */ case class StringTreeConcat( - override val children: ListBuffer[StringTreeElement] + override val children: ListBuffer[StringTreeElement] ) extends StringTreeElement(children) /** @@ -197,7 +263,7 @@ case class StringTreeConcat( * a (sub) string. */ case class StringTreeOr( - override val children: ListBuffer[StringTreeElement] + override val children: ListBuffer[StringTreeElement] ) extends StringTreeElement(children) /** @@ -209,7 +275,7 @@ case class StringTreeOr( * string may have (contain) a particular but not necessarily. */ case class StringTreeCond( - override val children: ListBuffer[StringTreeElement] + override val children: ListBuffer[StringTreeElement] ) extends StringTreeElement(children) /** @@ -220,5 +286,5 @@ case class StringTreeCond( * expression and that represents part of the value(s) a string may have. */ case class StringTreeConst( - sci: StringConstancyInformation + sci: StringConstancyInformation ) extends StringTreeElement(ListBuffer()) From 1aa3ca44776a1e726ad8c6ae70cdaf4153ac6071 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 11 Nov 2018 16:19:34 +0100 Subject: [PATCH 024/316] Extended the string definition analysis in a way that it can now correctly process the new test case 'ifElseInLoopWithAppendAfterwards'. For this, a little bug in StringTree#groupRepetitionElementsAcc had to be fixed. --- .../string_definition/TestMethods.java | 19 +++ .../NewStringBuilderProcessor.scala | 123 +++++++++++++++++- .../properties/StringTree.scala | 3 + 3 files changed, 141 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index d0d3e9fc94..46fb211a2c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -292,6 +292,25 @@ public void ifElseInLoopWithKnownBounds() { analyzeString(sb.toString()); } + @StringDefinitions( + value = "if-else control structure within a for loop and with an append afterwards", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "((x | [AnIntegerValue]))*yz" + ) + public void ifElseInLoopWithAppendAfterwards() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 20; i++) { + if (i % 2 == 0) { + sb.append("x"); + } else { + sb.append(i + 1); + } + } + sb.append("yz"); + + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "if-else control structure which append to a string builder multiple times", // expectedLevel = StringConstancyLevel.CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 35b7a9d6bc..39bed2855a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -42,7 +42,7 @@ class NewStringBuilderProcessor( val uses = assignment.targetVar.usedBy.filter(!ignore.contains(_)).toArray.sorted val (inits, nonInits) = getInitsAndNonInits(uses, stmts, cfg) val initTreeNodes = ListBuffer[StringTree]() - val nonInitTreeNodes = ListBuffer[StringTree]() + val nonInitTreeNodes = mutable.Map[Int, ListBuffer[StringTree]]() inits.foreach { next ⇒ val toProcess = stmts(next) match { @@ -79,7 +79,12 @@ class NewStringBuilderProcessor( nonInits.foreach { next ⇒ val subtree = exprHandler.concatDefSites(next) if (subtree.isDefined) { - nonInitTreeNodes.append(subtree.get) + val key = next.min + if (nonInitTreeNodes.contains(key)) { + nonInitTreeNodes(key).append(subtree.get) + } else { + nonInitTreeNodes(key) = ListBuffer(subtree.get) + } } } @@ -90,8 +95,10 @@ class NewStringBuilderProcessor( // Append nonInitTreeNodes to initTreeNodes (as children) if (nonInitTreeNodes.nonEmpty) { val toAppend = nonInitTreeNodes.size match { - case 1 ⇒ nonInitTreeNodes.head - case _ ⇒ StringTreeOr(nonInitTreeNodes) + // If there is only one element in the map use this + case 1 ⇒ nonInitTreeNodes.head._2.head + // Otherwise, we need to build the proper tree, considering dominators + case _ ⇒ orderNonInitNodes(nonInitTreeNodes, cfg) } if (initTreeNodes.isEmpty) { initTreeNodes.append(toAppend) @@ -183,4 +190,112 @@ class NewStringBuilderProcessor( (inits.toList.sorted, uniqueLists.map(_.toList)) } + /** + * The relation of nodes is not obvious, i.e., one cannot generally say that two nodes, which, + * e.g., modify a [[StringBuilder]] object are concatenations or are to be mapped to a + * [[StringTreeOr]]. + *

+ * This function uses the following algorithm to determine the relation of the given nodes: + *

    + *
  1. + * For each given node, compute the dominators (and store it in lists called + * ''dominatorLists'').
  2. + *
  3. + * Take all ''dominatorLists'' and union them (without duplicates) and sort this list in + * descending order. This list, called ''uniqueDomList'', is then used in the next step. + *
  4. + *
  5. + * Traverse ''uniqueDomList''. Here, we call the next element in that list ''nextDom''. + * One of the following cases will occur: + *
      + *
    1. + * Only one [[StringTree]] element in ''treeNodes'' has ''nextDom'' as a + * dominator. In this case, nothing is done as we cannot say anything about the + * relation to other elements in ''treeNodes''. + *
    2. + *
    3. + * At least two elements in ''treeNodes'' have ''nextDom'' as a dominator. In this + * case, these nodes are in a 'only-one-node-is-evaluated-relation' as it happens + * when the dominator of two nodes is an if condition, for example. Thus, these + * elements are put into a [[StringTreeOr]] element and then no longer considered + * for the computation. + *
    4. + *
    + *
  6. + *
  7. + * It might be that not all elements in ''treeNodes'' were processed by the second case of + * the previous step (normally, this should be only one element. These elements represent + * a concatenation relation. Thus, all [[StringTreeOr]] elements of the last step are then + * put into a [[StringTreeConcat]] element with the ones not processed yet. They are + * ordered in ascending order according to their index in the statement list to preserve + * the correct order. + *
  8. + *
+ * + * @param treeNodes The nodes which are to be ordered. The key of the map refers to the index in + * the statement list. It might be that some operation (e.g., append) have two + * definition sites; In this case, pass the minimum index. The values of the + * map correspond to [[StringTree]] elements that resulted from the evaluation + * of the definition site(s). + * @param cfg The control flow graph. + * @return This function computes the correct relation of the given [[StringTree]] elements + * and returns this as a single tree element. + */ + private def orderNonInitNodes( + treeNodes: mutable.Map[Int, ListBuffer[StringTree]], + cfg: CFG[Stmt[V], TACStmts[V]] + ): StringTree = { + // TODO: Put this function in a different place (like DominatorTreeUtils) and generalize the procedure. + val domTree = cfg.dominatorTree + // For each list of nodes, get the dominators + val rootIndex = cfg.startBlock.startPC + val dominatorLists = mutable.Map[Int, List[Int]]() + for ((key, _) ← treeNodes) { + val dominators = ListBuffer[Int]() + var next = domTree.immediateDominators(key) + while (next != rootIndex) { + dominators.prepend(next) + next = domTree.immediateDominators(next) + } + dominators.prepend(rootIndex) + dominatorLists(key) = dominators.toList + } + + // Build a unique union of the dominators that is in descending order + var uniqueDomList = ListBuffer[Int]() + for ((_, value) ← dominatorLists) { + uniqueDomList.append(value: _*) + } + uniqueDomList = uniqueDomList.distinct.sorted.reverse + + val newChildren = ListBuffer[StringTree]() + uniqueDomList.foreach { nextDom ⇒ + // Find elements with the same dominator + val indicesWithSameDom = ListBuffer[Int]() + for ((key, value) ← dominatorLists) { + if (value.contains(nextDom)) { + indicesWithSameDom.append(key) + } + } + if (indicesWithSameDom.size > 1) { + val newOrElement = ListBuffer[StringTree]() + // Sort in order to have the correct order + indicesWithSameDom.sorted.foreach { nextIndex ⇒ + newOrElement.append(treeNodes(nextIndex).head) + dominatorLists.remove(nextIndex) + } + newChildren.append(StringTreeOr(newOrElement)) + } + } + + // If there are elements left, add them as well (they represent concatenations) + for ((key, _) ← dominatorLists) { newChildren.append(treeNodes(key).head) } + + if (newChildren.size == 1) { + newChildren.head + } else { + StringTreeConcat(newChildren) + } + } + } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 3631888be6..9a6df26c7c 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -137,6 +137,9 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme val repetitionElements = newChildren.filter(_.isInstanceOf[StringTreeRepetition]) // Nothing to do when less than two repetition elements if (repetitionElements.length <= 1) { + // In case there is only one (new) repetition element, replace the children + subtree.children.clear() + subtree.children.append(newChildren: _*) subtree } else { val childrenOfReps = repetitionElements.map( From 9f0c37cc50286410a6c86b7fbe5b71311ce4ec04 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 12 Nov 2018 14:48:33 +0100 Subject: [PATCH 025/316] Added a test case with an if-block that does not have an else block and extended the string definition analysis to cover that case. For that, the post-dominator tree is required. Moreover, the simplification of StringTrees had to be refined. --- .../string_definition/TestMethods.java | 27 +++++++++---------- .../NewStringBuilderProcessor.scala | 14 ++++++++-- .../src/main/scala/org/opalj/br/cfg/CFG.scala | 25 +++++++++++++++++ .../properties/StringTree.scala | 16 +++++++++-- .../org/opalj/graphs/PostDominatorTree.scala | 23 ++++++++++++++++ 5 files changed, 87 insertions(+), 18 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 46fb211a2c..c2f7ce95f4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -311,20 +311,19 @@ public void ifElseInLoopWithAppendAfterwards() { analyzeString(sb.toString()); } - // @StringDefinitions( - // value = "if-else control structure which append to a string builder multiple times", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(b)+" - // ) - // public void ifElseWithStringBuilder3() { - // StringBuilder sb = new StringBuilder("a"); - // int i = new Random().nextInt(); - // if (i % 2 == 0) { - // sb.append("b"); - // } - // analyzeString(sb.toString()); - - // } + @StringDefinitions( + value = "if control structure without an else", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b)+" + ) + public void ifWithoutElse() { + StringBuilder sb = new StringBuilder("a"); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb.append("b"); + } + analyzeString(sb.toString()); + } private String getRuntimeClassName() { return "java.lang.Runtime"; diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 39bed2855a..45da3a5275 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -7,6 +7,7 @@ import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.Stmt import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.StringTreeConcat +import org.opalj.fpcf.string_definition.properties.StringTreeCond import org.opalj.fpcf.string_definition.properties.StringTreeConst import org.opalj.fpcf.string_definition.properties.StringTreeOr import org.opalj.fpcf.string_definition.properties.StringTreeRepetition @@ -95,8 +96,17 @@ class NewStringBuilderProcessor( // Append nonInitTreeNodes to initTreeNodes (as children) if (nonInitTreeNodes.nonEmpty) { val toAppend = nonInitTreeNodes.size match { - // If there is only one element in the map use this - case 1 ⇒ nonInitTreeNodes.head._2.head + // If there is only one element in the map use it; but first check the + // relation between that element and the init element + case 1 ⇒ + val postDomTree = cfg.postDominatorTree + if (initTreeNodes.nonEmpty && + !postDomTree.doesPostDominate(nonInits.head.head, inits.head)) { + // If not post-dominated, it is optional => Put it in a Cond + StringTreeCond(nonInitTreeNodes.head._2) + } else { + nonInitTreeNodes.head._2.head + } // Otherwise, we need to build the proper tree, considering dominators case _ ⇒ orderNonInitNodes(nonInitTreeNodes, cfg) } diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 2872fb960c..2c682cf637 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -7,6 +7,8 @@ import scala.reflect.ClassTag import java.util.Arrays +import org.opalj.collection.immutable.EmptyIntTrieSet + import scala.collection.{Set ⇒ SomeSet} import scala.collection.AbstractIterator @@ -20,6 +22,7 @@ import org.opalj.collection.mutable.IntArrayStack import org.opalj.graphs.DefaultMutableNode import org.opalj.graphs.DominatorTree import org.opalj.graphs.Node +import org.opalj.graphs.PostDominatorTree import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -788,6 +791,28 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( naturalLoops.get } + /** + * @return Returns the post dominator tree of this CFG. + * + * @see [[PostDominatorTree.apply]] + */ + def postDominatorTree: PostDominatorTree = { + val exitNodes = basicBlocks.zipWithIndex.filter(_._1.successors.size == 1).map(_._2) + PostDominatorTree( + if (exitNodes.length == 1) Some(exitNodes.head) else None, + i ⇒ exitNodes.contains(i), + // TODO: Pass an IntTrieSet if exitNodes contains more than one element + EmptyIntTrieSet, + // TODO: Correct function (just copied it from one of the tests)? + (f: Int ⇒ Unit) ⇒ exitNodes.foreach(e ⇒ f(e)), + foreachSuccessor, + foreachPredecessor, + basicBlocks.foldLeft(0) { (prevMaxNode: Int, next: BasicBlock) ⇒ + math.max(prevMaxNode, next.endPC) + } + ) + } + // --------------------------------------------------------------------------------------------- // // Visualization & Debugging diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 9a6df26c7c..5147c80df0 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -59,7 +59,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme s"${o.possibleStrings} | ${n.possibleStrings}" )) StringConstancyInformation( - reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})" + reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})+" ) case StringTreeConst(sci) ⇒ sci @@ -113,7 +113,19 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme subtree.children.clear() subtree.children.appendAll(unique) subtree - case _ ⇒ subtree + case stc: StringTreeCond ⇒ + // If the child of a StringTreeCond is a StringTreeRepetition, replace the + // StringTreeCond by the StringTreeRepetition element (otherwise, regular + // expressions like ((s)*)+ will follow which is equivalent to (s)*). + if (stc.children.nonEmpty && stc.children.head.isInstanceOf[StringTreeRepetition]) { + stc.children.head + } else { + stc + } + // Remaining cases are trivial + case str: StringTreeRepetition ⇒ StringTreeRepetition(simplifyAcc(str.child)) + case stc: StringTreeConcat ⇒ StringTreeConcat(stc.children.map(simplifyAcc)) + case stc: StringTreeConst ⇒ stc } } diff --git a/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala b/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala index f61c33c687..c259e61c0f 100644 --- a/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala +++ b/OPAL/common/src/main/scala/org/opalj/graphs/PostDominatorTree.scala @@ -4,6 +4,8 @@ package graphs import org.opalj.collection.immutable.IntTrieSet +import scala.collection.mutable.ListBuffer + /** * A representation of a post-dominator tree (see [[PostDominatorTree$#apply*]] * for details regarding the properties). @@ -35,6 +37,27 @@ final class PostDominatorTree private[graphs] ( */ def isAugmented: Boolean = hasVirtualStartNode + /** + * Checks whether ''node1'' post-dominates ''node2''. + * + * @param node1 The index of the first node. + * @param node2 The index of the second node. + * @return Returns true if the node whose index corresponds to ''node1'' post-dominates the node + * whose index corresponds to ''node2''. Otherwise false will be returned. + */ + def doesPostDominate(node1: Int, node2: Int): Boolean = { + // Get all post-dominators of node2 (including node2) + val postDominators = ListBuffer[Int](node2) + var nextPostDom = idom(node2) + while (nextPostDom != idom(nextPostDom)) { + postDominators.append(nextPostDom) + nextPostDom = idom(nextPostDom) + } + postDominators.append(nextPostDom) + + postDominators.contains(node1) + } + } /** From ca8cd352b56c854ef26e02c007da47fced516d52 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 14 Nov 2018 09:18:52 +0100 Subject: [PATCH 026/316] The construction of the PostDominatorTree was partly incorrect. --- OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 2c682cf637..91f773de73 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -797,7 +797,9 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( * @see [[PostDominatorTree.apply]] */ def postDominatorTree: PostDominatorTree = { - val exitNodes = basicBlocks.zipWithIndex.filter(_._1.successors.size == 1).map(_._2) + val exitNodes = basicBlocks.zipWithIndex.filter { next ⇒ + next._1.successors.size == 1 && next._1.successors.head.isInstanceOf[ExitNode] + }.map(_._2) PostDominatorTree( if (exitNodes.length == 1) Some(exitNodes.head) else None, i ⇒ exitNodes.contains(i), From dc3d1d2fd320d01673ec6faa02a2b9755ac11f74 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 16:43:49 +0100 Subject: [PATCH 027/316] Use a question mark instead of a plus sign to indicate that a string can occur zero or one time. --- .../org/opalj/fpcf/fixtures/string_definition/TestMethods.java | 2 +- .../opalj/fpcf/string_definition/properties/StringTree.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index c2f7ce95f4..b9a44d7bdd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -314,7 +314,7 @@ public void ifElseInLoopWithAppendAfterwards() { @StringDefinitions( value = "if control structure without an else", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)+" + expectedStrings = "a(b)?" ) public void ifWithoutElse() { StringBuilder sb = new StringBuilder("a"); diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 5147c80df0..97b5cc82a6 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -59,7 +59,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme s"${o.possibleStrings} | ${n.possibleStrings}" )) StringConstancyInformation( - reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})+" + reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})?" ) case StringTreeConst(sci) ⇒ sci From 541eccb80c889fff7d913bf58525238f0c484cd9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 16:45:40 +0100 Subject: [PATCH 028/316] Between the | sign (coming from ORs) there should be no space (otherwise ambiguities can arise). --- .../string_definition/TestMethods.java | 22 +++++++++---------- .../properties/StringTree.scala | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index b9a44d7bdd..5669f005cd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -104,8 +104,8 @@ public void fromConstantAndFunctionCall() { @StringDefinitions( value = "array access with unknown index", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(java.lang.String | java.lang.StringBuilder | " - + "java.lang.System | java.lang.Runnable)" + expectedStrings = "(java.lang.String|java.lang.StringBuilder|" + + "java.lang.System|java.lang.Runnable)" ) public void fromStringArray(int index) { String[] classes = { @@ -120,7 +120,7 @@ public void fromStringArray(int index) { @StringDefinitions( value = "a simple case where multiple definition sites have to be considered", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(java.lang.System | java.lang.Runtime)" + expectedStrings = "(java.lang.System|java.lang.Runtime)" ) public void multipleConstantDefSites(boolean cond) { String s; @@ -136,7 +136,7 @@ public void multipleConstantDefSites(boolean cond) { value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(java.lang.Object | \\w | java.lang.System | java.lang.\\w)" + expectedStrings = "(java.lang.Object|\\w|java.lang.System|java.lang.\\w)" ) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -165,7 +165,7 @@ public void multipleDefSites(int value) { @StringDefinitions( value = "if-else control structure which append to a string builder with an int expr", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(x | [AnIntegerValue])" + expectedStrings = "(x|[AnIntegerValue])" ) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb = new StringBuilder(); @@ -181,7 +181,7 @@ public void ifElseWithStringBuilderWithIntExpr() { @StringDefinitions( value = "if-else control structure which append to a string builder with an int", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "([AnIntegerValue] | x)" + expectedStrings = "([AnIntegerValue]|x)" ) public void ifElseWithStringBuilderWithConstantInt() { StringBuilder sb = new StringBuilder(); @@ -197,7 +197,7 @@ public void ifElseWithStringBuilderWithConstantInt() { @StringDefinitions( value = "if-else control structure which append to a string builder", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(a | b)" + expectedStrings = "(a|b)" ) public void ifElseWithStringBuilder1() { StringBuilder sb; @@ -213,7 +213,7 @@ public void ifElseWithStringBuilder1() { @StringDefinitions( value = "if-else control structure which append to a string builder", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b | c)" + expectedStrings = "a(b|c)" ) public void ifElseWithStringBuilder2() { StringBuilder sb = new StringBuilder("a"); @@ -229,7 +229,7 @@ public void ifElseWithStringBuilder2() { @StringDefinitions( value = "if-else control structure which append to a string builder multiple times", expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(bcd | xyz)" + expectedStrings = "a(bcd|xyz)" ) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); @@ -277,7 +277,7 @@ public void simpleForLoopWithUnknownBounds() { @StringDefinitions( value = "if-else control structure within a for loop with known loop bounds", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "((x | [AnIntegerValue]))*" + expectedStrings = "((x|[AnIntegerValue]))*" ) public void ifElseInLoopWithKnownBounds() { StringBuilder sb = new StringBuilder(); @@ -295,7 +295,7 @@ public void ifElseInLoopWithKnownBounds() { @StringDefinitions( value = "if-else control structure within a for loop and with an append afterwards", expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "((x | [AnIntegerValue]))*yz" + expectedStrings = "((x|[AnIntegerValue]))*yz" ) public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 97b5cc82a6..cf7dbd36c1 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -47,7 +47,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme StringConstancyLevel.determineMoreGeneral( old.constancyLevel, next.constancyLevel ), - old.possibleStrings+" | "+next.possibleStrings + old.possibleStrings+"|"+next.possibleStrings ) } StringConstancyInformation(reduced.constancyLevel, s"(${reduced.possibleStrings})") @@ -56,7 +56,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme val scis = c.map(reduceAcc) val reducedInfo = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - s"${o.possibleStrings} | ${n.possibleStrings}" + s"${o.possibleStrings}|${n.possibleStrings}" )) StringConstancyInformation( reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})?" From cebdc342173c7b918cbd65a5be29b313386d26a6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 16:55:43 +0100 Subject: [PATCH 029/316] Added a documentation to the TestMethods class that describes which strings / characters are used when reducing a StringTree and thus be better avoided in expectedStrings. --- .../string_definition/TestMethods.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 5669f005cd..ed35ab49b1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -7,6 +7,33 @@ import java.util.Random; /** + * This file contains various tests for the StringDefinitionAnalysis. The following things are to be + * considered when adding test cases: + *
    + *
  • + * The asterisk symbol (*) is used to indicate that a string (or part of it) can occur >= 0 times. + *
  • + *
  • + * Question marks (?) are used to indicate that a string (or part of it) can occur either zero + * times or once. + *
  • + *
  • + * The string "\w" is used to indicate that a string (or part of it) is unknown / arbitrary, i.e., + * it cannot be approximated. + *
  • + *
  • + * The pipe symbol is used to indicate that a string (or part of it) consists of one of several + * options (but definitely one of these values). + *
  • + *
  • + * Brackets ("(" and "(") are used for nesting and grouping string expressions. + *
  • + *
+ *

+ * Thus, you should avoid the following characters / strings to occur in "expectedStrings": + * {*, ?, \w, |}. In the future, "expectedStrings" might be parsed back into a StringTree. Thus, to + * be on the safe side, brackets should be avoided as well. + * * @author Patrick Mell */ public class TestMethods { From a8d2d5be91870b6dc47a60886a550505de1ce52d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 16 Nov 2018 17:18:37 +0100 Subject: [PATCH 030/316] Added more test cases (that we talked about in the last meeting) and which are currently not supported by the analysis (thus, commented out). --- .../string_definition/TestMethods.java | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index ed35ab49b1..6b370d5f9c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -106,6 +106,22 @@ public void directAppendConcats() { analyzeString(sb.toString()); } + // @StringDefinitions( + // value = "checks if a string value with > 2 continuous appends and a second " + // + "StringBuilder is determined correctly", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "java.langStringB." + // ) + // public void directAppendConcats2() { + // StringBuilder sb = new StringBuilder("java"); + // StringBuilder sb2 = new StringBuilder("B"); + // sb.append(".").append("lang"); + // sb2.append("."); + // sb.append("String"); + // sb.append(sb2.toString()); + // analyzeString(sb.toString()); + // } + @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", expectedLevel = StringConstancyLevel.DYNAMIC, @@ -189,6 +205,47 @@ public void multipleDefSites(int value) { analyzeString(s); } + // @StringDefinitions( + // value = "a case where multiple optional definition sites have to be considered.", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(b|c)?" + // ) + // public void multipleOptionalAppendSites(int value) { + // StringBuilder sb = new StringBuilder("a"); + // switch (value) { + // case 0: + // sb.append("b"); + // break; + // case 1: + // sb.append("c"); + // break; + // case 3: + // break; + // case 4: + // break; + // } + // analyzeString(sb.toString()); + // } + + // @StringDefinitions( + // value = "a case with a switch with missing breaks", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(bc|c)?" + // ) + // public void multipleOptionalAppendSites(int value) { + // StringBuilder sb = new StringBuilder("a"); + // switch (value) { + // case 0: + // sb.append("b"); + // case 1: + // sb.append("c"); + // break; + // case 2: + // break; + // } + // analyzeString(sb.toString()); + // } + @StringDefinitions( value = "if-else control structure which append to a string builder with an int expr", expectedLevel = StringConstancyLevel.DYNAMIC, @@ -352,6 +409,140 @@ public void ifWithoutElse() { analyzeString(sb.toString()); } + // @StringDefinitions( + // value = "an extensive example with many control structures", + // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + // ) + // public void extensive(boolean cond) { + // StringBuilder sb = new StringBuilder(); + // if (cond) { + // sb.append("iv1"); + // } else { + // sb.append("iv2"); + // } + // System.out.println(sb); + // sb.append(": "); + // + // Random random = new Random(); + // while (random.nextFloat() > 5.) { + // if (random.nextInt() % 2 == 0) { + // sb.append("great!"); + // } + // } + // + // if (sb.indexOf("great!") > -1) { + // sb.append(getRuntimeClassName()); + // } + // + // analyzeString(sb.toString()); + // } + + // @StringDefinitions( + // value = "an extensive example with many control structures where appends follow " + // + "after the read", + // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // expectedStrings = "(iv1|iv2): " + // ) + // public void extensiveEarlyRead(boolean cond) { + // StringBuilder sb = new StringBuilder(); + // if (cond) { + // sb.append("iv1"); + // } else { + // sb.append("iv2"); + // } + // System.out.println(sb); + // sb.append(": "); + // + // analyzeString(sb.toString()); + // + // Random random = new Random(); + // while (random.nextFloat() > 5.) { + // if (random.nextInt() % 2 == 0) { + // sb.append("great!"); + // } + // } + // + // if (sb.indexOf("great!") > -1) { + // sb.append(getRuntimeClassName()); + // } + // } + + // @StringDefinitions( + // value = "a case where a StringBuffer is used (instead of a StringBuilder)", + // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + // ) + // public void extensiveStringBuffer(boolean cond) { + // StringBuffer sb = new StringBuffer(); + // if (cond) { + // sb.append("iv1"); + // } else { + // sb.append("iv2"); + // } + // System.out.println(sb); + // sb.append(": "); + // + // Random random = new Random(); + // while (random.nextFloat() > 5.) { + // if (random.nextInt() % 2 == 0) { + // sb.append("great!"); + // } + // } + // + // if (sb.indexOf("great!") > -1) { + // sb.append(getRuntimeClassName()); + // } + // + // analyzeString(sb.toString()); + // } + + // @StringDefinitions( + // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " + // + "Michael Eichberg)?", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(b)*" + // ) + // public void whileTrue() { + // StringBuilder sb = new StringBuilder("a"); + // while (true) { + // sb.append("b"); + // } + // analyzeString(sb.toString()); + // } + + // @StringDefinitions( + // value = "case with a nested loop where in the outer loop a StringBuilder is created " + // + "that is later read (TODO: As Michael Eichberg meant?)", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(b)*" + // ) + // public void nestedLoops(int range) { + // for (int i = 0; i < range; i++) { + // StringBuilder sb = new StringBuilder("a"); + // for (int j = 0; j < range * range; j++) { + // sb.append("b"); + // } + // analyzeString(sb.toString()); + // } + // } + + // @StringDefinitions( + // value = "case with an exception", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "(File Content: |File Content: *)" + // ) + // public void withException(String filename) { + // StringBuilder sb = new StringBuilder("File Content: "); + // try { + // String data = new String(Files.readAllBytes(Paths.get(filename))); + // sb.append(data); + // } catch (Exception ignore) { + // } finally { + // analyzeString(sb.toString()); + // } + // } + private String getRuntimeClassName() { return "java.lang.Runtime"; } From 94051207c7ccb057ea3d5021f75e051dc1d625bf Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 21 Nov 2018 12:19:48 +0100 Subject: [PATCH 031/316] Implemented a way to find all paths in a CFG from a set of given starting points. --- .../analyses/string_definition/package.scala | 6 + .../preprocessing/AbstractPathFinder.scala | 121 ++++++++++++++++ .../preprocessing/DefaultPathFinder.scala | 137 ++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala index 9c503d9b51..2bd2ceaaf0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala @@ -2,6 +2,7 @@ package org.opalj.fpcf.analyses import org.opalj.br.Method +import org.opalj.fpcf.analyses.string_definition.preprocessing.SubPath import org.opalj.tac.DUVar import org.opalj.value.ValueInformation @@ -23,4 +24,9 @@ package object string_definition { */ type P = (V, Method) + /** + * TODO: Comment + */ + type Path = List[SubPath] + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala new file mode 100644 index 0000000000..b7721d8663 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -0,0 +1,121 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.preprocessing + +import org.opalj.br.cfg.CFG +import org.opalj.br.cfg.CFGNode +import org.opalj.fpcf.analyses.string_definition.Path +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +/** + * [[SubPath]] represents the general item that forms a [[Path]]. + */ +sealed class SubPath() + +/** + * A flat element, e.g., for representing a single statement. The statement is identified by + * `element`. + */ +case class FlatPathElement(element: Int) extends SubPath + +/** + * A nested path element, that is, items can be used to form arbitrary structures / hierarchies. + * `element` holds all child elements. + */ +case class NestedPathElement(element: ListBuffer[SubPath]) extends SubPath + +/** + * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the + * scope of string definition analyses. + * + * @author Patrick Mell + */ +trait AbstractPathFinder { + + /** + * Generates a new [[NestedPathElement]] with a given number of inner [[NestedPathElement]]s. + */ + protected def generateNestPathElement(numInnerElements: Int): NestedPathElement = { + val outerNested = NestedPathElement(ListBuffer()) + for (_ ← 0.until(numInnerElements)) { + outerNested.element.append(NestedPathElement(ListBuffer())) + } + outerNested + } + + /** + * Determines whether a given `site` is the head of a loop by comparing it to a set of loops + * (here a list of lists). This function returns ''true'', if `site` is the head of one of the + * inner lists. + */ + protected def isHeadOfLoop(site: Int, loops: List[List[Int]]): Boolean = + loops.foldLeft(false)((old: Boolean, nextLoop: List[Int]) ⇒ old || nextLoop.head == site) + + /** + * Determines whether a given `site` is the end of a loop by comparing it to a set of loops + * (here a list of lists). This function returns ''true'', if `site` is the last element of one + * of the inner lists. + */ + protected def isEndOfLoop(site: Int, loops: List[List[Int]]): Boolean = + loops.foldLeft(false)((old: Boolean, nextLoop: List[Int]) ⇒ old || nextLoop.last == site) + + /** + * This function checks if a branching corresponds to an if (or if-elseif) structure that has no + * else block. + * Currently, this function is implemented to check whether the very last element of + * `successors` is a path past the if (or if-elseif) paths. + * + * @param successors The successors of a branching. + * @param cfg The control flow graph underlying the successors. + * @return Returns ''true'', if the very last element of `successors` is a child of one of the + * other successors. If this is the case, the branching corresponds to one without an + * ''else'' branch. + */ + def isCondWithoutElse(successors: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + // Separate the last element from all previous ones + val branches = successors.reverse.tail.reverse + val lastEle = successors.last + + // For every successor (except the very last one), execute a DFS to check whether the very + // last element is a successor. If so, this represents a path past the if (or if-elseif). + branches.count { next ⇒ + val seenNodes = ListBuffer[CFGNode](cfg.bb(next)) + val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) + while (toVisitStack.nonEmpty) { + val from = toVisitStack.pop() + val to = from.successors + if (to.contains(cfg.bb(lastEle))) { + return true + } + seenNodes.append(from) + toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) + } + return false + } > 1 + } + + /** + * Implementations of this function find all paths starting from the sites, given by + * `startSites`, within the provided control flow graph, `cfg`. As this is executed within the + * context of a string definition analysis, implementations are free to decide whether they + * include only statements that work on [[StringBuffer]] / [[StringBuilder]] or include all + * statements in the paths. + * + * @param startSites A list of possible start sites, that is, initializations. Several start + * sites denote that an object is initialized within a conditional. + * @param endSite An end site which an implementation might use to early-stop the procedure. + * This site can be the read operation of interest, for instance. + * @param cfg The underlying control flow graph which servers as the basis to find the paths. + * @return Returns all found paths as a [[Path]] object. That means, the return object is a flat + * structure, however, captures all hierarchies and (nested) flows. Note that a + * [[NestedPathElement]] with only one child can either refer to a loop or an ''if'' + * that has no ''else'' block (from a high-level perspective). It is the job of the the + * procedure processing the return object to further identify that (if necessary). + */ + def findPaths(startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Path + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala new file mode 100644 index 0000000000..dea4b85562 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -0,0 +1,137 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.preprocessing + +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.br.cfg.CFG +import org.opalj.br.cfg.ExitNode +import org.opalj.fpcf.analyses.string_definition.Path + +import scala.collection.mutable.ListBuffer + +/** + * An approach based on an a naive / intuitive traversing of the control flow graph. + * + * @author Patrick Mell + */ +class DefaultPathFinder extends AbstractPathFinder { + + /** + * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` + * and, based on that, determines in what relation a statement / instruction is with its + * predecessors / successors. + * The paths contain all instructions, not only those that modify a [[StringBuilder]] / + * [[StringBuffer]] object. + * For this implementation, `endSite` is not required, thus passing any value is fine. + * + * @see [[AbstractPathFinder.findPaths]] + */ + override def findPaths( + startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]] + ): Path = { + // path will accumulate all paths + val path = ListBuffer[SubPath]() + val stack = ListBuffer[Int](startSites: _*) + val seenElements = ListBuffer[Int]() + // numSplits serves a queue that stores the number of possible branches (or successors) + val numSplits = ListBuffer[Int]() + // Also a queue that stores the indices of which branch of a conditional to take next + val currSplitIndex = ListBuffer[Int]() + // Used to quickly find the element at which to insert a sub path + val nestedElementsRef = ListBuffer[NestedPathElement]() + val natLoops = cfg.findNaturalLoops() + + // Multiple start sites => We start within a conditional => Prepare for that + if (startSites.size > 1) { + val outerNested = generateNestPathElement(startSites.size) + numSplits.append(startSites.size) + currSplitIndex.append(0) + nestedElementsRef.append(outerNested) + path.append(outerNested) + } + + while (stack.nonEmpty) { + val popped = stack.head + stack.remove(0) + val bb = cfg.bb(popped) + val isLoopHeader = isHeadOfLoop(popped, natLoops) + var isLoopEnding = false + var belongsToLoopHeader = false + + // Append everything of the current basic block to the path + for (i ← bb.startPC.to(bb.endPC)) { + seenElements.append(i) + val toAppend = FlatPathElement(i) + + if (!isLoopEnding) { + isLoopEnding = isEndOfLoop(i, natLoops) + } + + // For loop headers, insert a new nested element (and thus, do the housekeeping) + if (isHeadOfLoop(i, natLoops)) { + numSplits.prepend(1) + currSplitIndex.prepend(0) + + val outer = generateNestPathElement(1) + outer.element.head.asInstanceOf[NestedPathElement].element.append(toAppend) + nestedElementsRef.prepend(outer) + path.append(outer) + + belongsToLoopHeader = true + } // The instructions belonging to a loop header are stored in a flat structure + else if (!belongsToLoopHeader && (numSplits.isEmpty || bb.predecessors.size > 1)) { + path.append(toAppend) + } // Within a nested structure => append to an inner element + else { + val relevantRef = nestedElementsRef.head.element(currSplitIndex.head) + relevantRef.asInstanceOf[NestedPathElement].element.append(toAppend) + } + } + + val successors = bb.successors.filter { + !_.isInstanceOf[ExitNode] + }.map(_.nodeId).toList.sorted + val successorsToAdd = successors.filter { next ⇒ + !seenElements.contains(next) && !stack.contains(next) + } + val hasSeenSuccessor = successors.foldLeft(false) { + (old: Boolean, next: Int) ⇒ old || seenElements.contains(next) + } + + // At the join point of a branching, do some housekeeping + if (currSplitIndex.nonEmpty && + ((bb.predecessors.size > 1 && !isLoopHeader) || hasSeenSuccessor)) { + currSplitIndex(0) += 1 + if (currSplitIndex.head == numSplits.head) { + numSplits.remove(0) + currSplitIndex.remove(0) + nestedElementsRef.remove(0) + } + } + + // Within a conditional, prepend in order to keep the correct order + if (numSplits.nonEmpty && (bb.predecessors.size == 1)) { + stack.prependAll(successorsToAdd) + } else { + stack.appendAll(successorsToAdd) + } + // On a split point, prepare the next (nested) element + if (successors.length > 1 && !isLoopHeader) { + val appendSite = if (numSplits.isEmpty) path else + nestedElementsRef(currSplitIndex.head).element + val relevantNumSuccessors = if (isCondWithoutElse(successors, cfg)) + successors.size - 1 else successors.size + val outerNested = generateNestPathElement(relevantNumSuccessors) + + numSplits.prepend(relevantNumSuccessors) + currSplitIndex.prepend(0) + nestedElementsRef.prepend(outerNested) + appendSite.append(outerNested) + } + } + + path.toList + } + +} From 3a7df1855513dc13c30339f79d2977b5ecfbb5e7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 21 Nov 2018 12:21:08 +0100 Subject: [PATCH 032/316] Added a method to find all initialization sites of (StringBuilder) objects. --- .../NewStringBuilderProcessor.scala | 112 ++++++++++++------ 1 file changed, 78 insertions(+), 34 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala index 45da3a5275..1aba0644d2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala @@ -16,6 +16,7 @@ import org.opalj.tac.Expr import org.opalj.tac.New import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualFunctionCall import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -207,39 +208,39 @@ class NewStringBuilderProcessor( *

* This function uses the following algorithm to determine the relation of the given nodes: *

    - *
  1. - * For each given node, compute the dominators (and store it in lists called - * ''dominatorLists'').
  2. - *
  3. - * Take all ''dominatorLists'' and union them (without duplicates) and sort this list in - * descending order. This list, called ''uniqueDomList'', is then used in the next step. - *
  4. - *
  5. - * Traverse ''uniqueDomList''. Here, we call the next element in that list ''nextDom''. - * One of the following cases will occur: - *
      - *
    1. - * Only one [[StringTree]] element in ''treeNodes'' has ''nextDom'' as a + *
    2. + * For each given node, compute the dominators (and store it in lists called + * ''dominatorLists'').
    3. + *
    4. + * Take all ''dominatorLists'' and union them (without duplicates) and sort this list in + * descending order. This list, called ''uniqueDomList'', is then used in the next step. + *
    5. + *
    6. + * Traverse ''uniqueDomList''. Here, we call the next element in that list ''nextDom''. + * One of the following cases will occur: + *
        + *
      1. + * Only one [[StringTree]] element in ''treeNodes'' has ''nextDom'' as a * dominator. In this case, nothing is done as we cannot say anything about the - * relation to other elements in ''treeNodes''. - *
      2. - *
      3. - * At least two elements in ''treeNodes'' have ''nextDom'' as a dominator. In this - * case, these nodes are in a 'only-one-node-is-evaluated-relation' as it happens - * when the dominator of two nodes is an if condition, for example. Thus, these - * elements are put into a [[StringTreeOr]] element and then no longer considered - * for the computation. - *
      4. - *
      - *
    7. - *
    8. - * It might be that not all elements in ''treeNodes'' were processed by the second case of - * the previous step (normally, this should be only one element. These elements represent - * a concatenation relation. Thus, all [[StringTreeOr]] elements of the last step are then - * put into a [[StringTreeConcat]] element with the ones not processed yet. They are - * ordered in ascending order according to their index in the statement list to preserve - * the correct order. - *
    9. + * relation to other elements in ''treeNodes''. + * + *
    10. + * At least two elements in ''treeNodes'' have ''nextDom'' as a dominator. In this + * case, these nodes are in a 'only-one-node-is-evaluated-relation' as it happens + * when the dominator of two nodes is an if condition, for example. Thus, these + * elements are put into a [[StringTreeOr]] element and then no longer considered + * for the computation. + *
    11. + *
    + *
  6. + *
  7. + * It might be that not all elements in ''treeNodes'' were processed by the second case of + * the previous step (normally, this should be only one element. These elements represent + * a concatenation relation. Thus, all [[StringTreeOr]] elements of the last step are then + * put into a [[StringTreeConcat]] element with the ones not processed yet. They are + * ordered in ascending order according to their index in the statement list to preserve + * the correct order. + *
  8. *
* * @param treeNodes The nodes which are to be ordered. The key of the map refers to the index in @@ -247,7 +248,7 @@ class NewStringBuilderProcessor( * definition sites; In this case, pass the minimum index. The values of the * map correspond to [[StringTree]] elements that resulted from the evaluation * of the definition site(s). - * @param cfg The control flow graph. + * @param cfg The control flow graph. * @return This function computes the correct relation of the given [[StringTree]] elements * and returns this as a single tree element. */ @@ -299,7 +300,9 @@ class NewStringBuilderProcessor( } // If there are elements left, add them as well (they represent concatenations) - for ((key, _) ← dominatorLists) { newChildren.append(treeNodes(key).head) } + for ((key, _) ← dominatorLists) { + newChildren.append(treeNodes(key).head) + } if (newChildren.size == 1) { newChildren.head @@ -309,3 +312,44 @@ class NewStringBuilderProcessor( } } + +object NewStringBuilderProcessor { + + /** + * Determines the definition site of the initialization of the base object that belongs to a + * ''toString'' call. + * + * @param toString The ''toString'' call of the object for which to get the initialization def + * site for. Make sure that the object is a subclass of + * [[AbstractStringBuilder]]. + * @param stmts A list of statements which will be used to lookup which one the initialization + * is. + * @return Returns the definition site of the base object of the call. If something goes wrong, + * e.g., no initialization is found, ''None'' is returned. + */ + def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { + // TODO: Check that we deal with an instance of AbstractStringBuilder + if (toString.name != "toString") { + return List() + } + + val defSites = ListBuffer[Int]() + val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.toArray: _*) + while (stack.nonEmpty) { + val next = stack.pop() + stmts(next) match { + case a: Assignment[V] ⇒ + a.expr match { + case _: New ⇒ + defSites.append(next) + case vfc: VirtualFunctionCall[V] ⇒ + stack.pushAll(vfc.receiver.asVar.definedBy.toArray) + } + case _ ⇒ + } + } + + defSites.sorted.toList + } + +} From af67dfa9b807d3ea5123853616aa85c09c48e483 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 22 Nov 2018 22:18:43 +0100 Subject: [PATCH 033/316] NestedPathElements can now have an associated type identifying the branching type (e.g., a loop). Also, a couple of bug fixes to improve the accuracy of the 'find paths' procedure. --- .../preprocessing/AbstractPathFinder.scala | 42 ++++++++++++----- .../preprocessing/DefaultPathFinder.scala | 47 +++++++++++++++---- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index b7721d8663..159d2e05cf 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -22,11 +22,22 @@ sealed class SubPath() */ case class FlatPathElement(element: Int) extends SubPath +/** + * Identifies the nature of a nested path element. + */ +object NestedPathType extends Enumeration { + val Loop, Conditional = Value +} + /** * A nested path element, that is, items can be used to form arbitrary structures / hierarchies. - * `element` holds all child elements. + * `element` holds all child elements. Path finders should set the `elementType` property whenever + * possible, i.e., when they compute / have this information. */ -case class NestedPathElement(element: ListBuffer[SubPath]) extends SubPath +case class NestedPathElement( + element: ListBuffer[SubPath], + elementType: Option[NestedPathType.Value] +) extends SubPath /** * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the @@ -39,10 +50,13 @@ trait AbstractPathFinder { /** * Generates a new [[NestedPathElement]] with a given number of inner [[NestedPathElement]]s. */ - protected def generateNestPathElement(numInnerElements: Int): NestedPathElement = { - val outerNested = NestedPathElement(ListBuffer()) + protected def generateNestPathElement( + numInnerElements: Int, + elementType: NestedPathType.Value + ): NestedPathElement = { + val outerNested = NestedPathElement(ListBuffer(), Some(elementType)) for (_ ← 0.until(numInnerElements)) { - outerNested.element.append(NestedPathElement(ListBuffer())) + outerNested.element.append(NestedPathElement(ListBuffer(), None)) } outerNested } @@ -66,16 +80,17 @@ trait AbstractPathFinder { /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no * else block. - * Currently, this function is implemented to check whether the very last element of - * `successors` is a path past the if (or if-elseif) paths. + * Currently, this function is implemented to check whether the very last element of the + * successors of the given site is a path past the if (or if-elseif) paths. * - * @param successors The successors of a branching. + * @param branchingSite The site / index of a branching that is to be checked. * @param cfg The control flow graph underlying the successors. - * @return Returns ''true'', if the very last element of `successors` is a child of one of the + * @return Returns ''true'', if the very last element of the successors is a child of one of the * other successors. If this is the case, the branching corresponds to one without an * ''else'' branch. */ - def isCondWithoutElse(successors: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + def isCondWithoutElse(branchingSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + val successors = cfg.bb(branchingSite).successors.map(_.nodeId).toArray.sorted // Separate the last element from all previous ones val branches = successors.reverse.tail.reverse val lastEle = successors.last @@ -83,7 +98,7 @@ trait AbstractPathFinder { // For every successor (except the very last one), execute a DFS to check whether the very // last element is a successor. If so, this represents a path past the if (or if-elseif). branches.count { next ⇒ - val seenNodes = ListBuffer[CFGNode](cfg.bb(next)) + val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() @@ -113,8 +128,9 @@ trait AbstractPathFinder { * @return Returns all found paths as a [[Path]] object. That means, the return object is a flat * structure, however, captures all hierarchies and (nested) flows. Note that a * [[NestedPathElement]] with only one child can either refer to a loop or an ''if'' - * that has no ''else'' block (from a high-level perspective). It is the job of the the - * procedure processing the return object to further identify that (if necessary). + * that has no ''else'' block (from a high-level perspective). It is the job of the + * implementations to attach these information to [[NestedPathElement]]s (so that + * procedures using results of this function do not need to re-process). */ def findPaths(startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Path diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index dea4b85562..db88982fef 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -44,7 +44,7 @@ class DefaultPathFinder extends AbstractPathFinder { // Multiple start sites => We start within a conditional => Prepare for that if (startSites.size > 1) { - val outerNested = generateNestPathElement(startSites.size) + val outerNested = generateNestPathElement(startSites.size, NestedPathType.Conditional) numSplits.append(startSites.size) currSplitIndex.append(0) nestedElementsRef.append(outerNested) @@ -57,6 +57,7 @@ class DefaultPathFinder extends AbstractPathFinder { val bb = cfg.bb(popped) val isLoopHeader = isHeadOfLoop(popped, natLoops) var isLoopEnding = false + var loopEndingIndex = -1 var belongsToLoopHeader = false // Append everything of the current basic block to the path @@ -65,7 +66,7 @@ class DefaultPathFinder extends AbstractPathFinder { val toAppend = FlatPathElement(i) if (!isLoopEnding) { - isLoopEnding = isEndOfLoop(i, natLoops) + isLoopEnding = isEndOfLoop(cfg.bb(i).endPC, natLoops) } // For loop headers, insert a new nested element (and thus, do the housekeeping) @@ -73,19 +74,38 @@ class DefaultPathFinder extends AbstractPathFinder { numSplits.prepend(1) currSplitIndex.prepend(0) - val outer = generateNestPathElement(1) - outer.element.head.asInstanceOf[NestedPathElement].element.append(toAppend) + val outer = generateNestPathElement(0, NestedPathType.Loop) + outer.element.append(toAppend) nestedElementsRef.prepend(outer) path.append(outer) belongsToLoopHeader = true + } // For loop ending, find the top-most loop from the stack and add to that element + else if (isLoopEnding) { + val loopElement = nestedElementsRef.find { + _.elementType match { + case Some(et) ⇒ et == NestedPathType.Loop + case _ ⇒ false + } + } + if (loopElement.isDefined) { + loopEndingIndex = nestedElementsRef.indexOf(loopElement.get) + loopElement.get.element.append(toAppend) + } } // The instructions belonging to a loop header are stored in a flat structure else if (!belongsToLoopHeader && (numSplits.isEmpty || bb.predecessors.size > 1)) { path.append(toAppend) } // Within a nested structure => append to an inner element else { - val relevantRef = nestedElementsRef.head.element(currSplitIndex.head) - relevantRef.asInstanceOf[NestedPathElement].element.append(toAppend) + // For loops + var ref: NestedPathElement = nestedElementsRef.head + // Refine for conditionals + ref.elementType match { + case Some(t) if t == NestedPathType.Conditional ⇒ + ref = ref.element(currSplitIndex.head).asInstanceOf[NestedPathElement] + case _ ⇒ + } + ref.element.append(toAppend) } } @@ -99,6 +119,13 @@ class DefaultPathFinder extends AbstractPathFinder { (old: Boolean, next: Int) ⇒ old || seenElements.contains(next) } + // Clean a loop from the stacks if the end of a loop was reached + if (loopEndingIndex != -1) { + numSplits.remove(loopEndingIndex) + currSplitIndex.remove(loopEndingIndex) + nestedElementsRef.remove(loopEndingIndex) + } + // At the join point of a branching, do some housekeeping if (currSplitIndex.nonEmpty && ((bb.predecessors.size > 1 && !isLoopHeader) || hasSeenSuccessor)) { @@ -116,13 +143,15 @@ class DefaultPathFinder extends AbstractPathFinder { } else { stack.appendAll(successorsToAdd) } - // On a split point, prepare the next (nested) element + // On a split point, prepare the next (nested) element (however, not for loop headers) if (successors.length > 1 && !isLoopHeader) { val appendSite = if (numSplits.isEmpty) path else nestedElementsRef(currSplitIndex.head).element - val relevantNumSuccessors = if (isCondWithoutElse(successors, cfg)) + val relevantNumSuccessors = if (isCondWithoutElse(popped, cfg)) successors.size - 1 else successors.size - val outerNested = generateNestPathElement(relevantNumSuccessors) + val outerNested = generateNestPathElement( + relevantNumSuccessors, NestedPathType.Conditional + ) numSplits.prepend(relevantNumSuccessors) currSplitIndex.prepend(0) From b6d87a2214571d1129ede7e4882449934da6be8e Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 23 Nov 2018 12:00:54 +0100 Subject: [PATCH 034/316] (1) Re-arranged the types used to model paths in the context of StringDefinitionAnalysis (2) A path can now be transformed into its lean equivalent, i.e., only relevant (for the StringDefinitionAnalysis) statements remain. --- .../analyses/string_definition/package.scala | 6 - .../preprocessing/AbstractPathFinder.scala | 29 ---- .../preprocessing/DefaultPathFinder.scala | 3 +- .../preprocessing/Path.scala | 143 ++++++++++++++++++ 4 files changed, 144 insertions(+), 37 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala index 2bd2ceaaf0..9c503d9b51 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala @@ -2,7 +2,6 @@ package org.opalj.fpcf.analyses import org.opalj.br.Method -import org.opalj.fpcf.analyses.string_definition.preprocessing.SubPath import org.opalj.tac.DUVar import org.opalj.value.ValueInformation @@ -24,9 +23,4 @@ package object string_definition { */ type P = (V, Method) - /** - * TODO: Comment - */ - type Path = List[SubPath] - } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 159d2e05cf..2923bb263a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -3,7 +3,6 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import org.opalj.br.cfg.CFG import org.opalj.br.cfg.CFGNode -import org.opalj.fpcf.analyses.string_definition.Path import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -11,34 +10,6 @@ import org.opalj.tac.TACStmts import scala.collection.mutable import scala.collection.mutable.ListBuffer -/** - * [[SubPath]] represents the general item that forms a [[Path]]. - */ -sealed class SubPath() - -/** - * A flat element, e.g., for representing a single statement. The statement is identified by - * `element`. - */ -case class FlatPathElement(element: Int) extends SubPath - -/** - * Identifies the nature of a nested path element. - */ -object NestedPathType extends Enumeration { - val Loop, Conditional = Value -} - -/** - * A nested path element, that is, items can be used to form arbitrary structures / hierarchies. - * `element` holds all child elements. Path finders should set the `elementType` property whenever - * possible, i.e., when they compute / have this information. - */ -case class NestedPathElement( - element: ListBuffer[SubPath], - elementType: Option[NestedPathType.Value] -) extends SubPath - /** * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the * scope of string definition analyses. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index db88982fef..5af6b2644c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -6,7 +6,6 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.br.cfg.CFG import org.opalj.br.cfg.ExitNode -import org.opalj.fpcf.analyses.string_definition.Path import scala.collection.mutable.ListBuffer @@ -160,7 +159,7 @@ class DefaultPathFinder extends AbstractPathFinder { } } - path.toList + Path(path.toList) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala new file mode 100644 index 0000000000..7fb8072f48 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -0,0 +1,143 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.preprocessing + +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.tac.DUVar +import org.opalj.tac.Stmt +import org.opalj.value.ValueInformation + +import scala.collection.mutable.ListBuffer + +/** + * @author Patrick Mell + */ + +/** + * [[SubPath]] represents the general item that forms a [[Path]]. + */ +sealed class SubPath() + +/** + * A flat element, e.g., for representing a single statement. The statement is identified by + * `element`. + */ +case class FlatPathElement(element: Int) extends SubPath + +/** + * Identifies the nature of a nested path element. + */ +object NestedPathType extends Enumeration { + val Loop, Conditional = Value +} + +/** + * A nested path element, that is, items can be used to form arbitrary structures / hierarchies. + * `element` holds all child elements. Path finders should set the `elementType` property whenever + * possible, i.e., when they compute / have this information. + */ +case class NestedPathElement( + element: ListBuffer[SubPath], + elementType: Option[NestedPathType.Value] +) extends SubPath + +/** + * Models a path by assembling it out of [[SubPath]] elements. + * + * @param elements The elements that belong to a path. + */ +case class Path(elements: List[SubPath]) { + + /** + * Takes an object of interest, `obj`, and a list of statements, `stmts` and finds all + * definitions and usages of `obj`within `stmts`. These sites are then returned in a single + * sorted list. + */ + private def getAllDefAndUseSites( + obj: DUVar[ValueInformation], stmts: Array[Stmt[V]] + ): List[Int] = { + val defAndUses = ListBuffer[Int]() + + obj.definedBy.foreach { d ⇒ + val defSites = stmts(d).asAssignment.expr.asVirtualFunctionCall.receiver.asVar.definedBy + defSites.foreach { innerDS ⇒ + defAndUses.append(innerDS) + defAndUses.append(stmts(innerDS).asAssignment.targetVar.usedBy.toArray.toList: _*) + } + } + + defAndUses.toList + } + + /** + * Accumulator function for transforming a path into its lean equivalent. This function turns + * [[NestedPathElement]]s into lean [[NestedPathElement]]s. In case a (sub) path is empty, + * `None` is returned and otherwise the lean (sub) path. + */ + private def makeLeanPathAcc( + toProcess: NestedPathElement, siteMap: Map[Int, Unit.type] + ): Option[NestedPathElement] = { + val elements = ListBuffer[SubPath]() + + toProcess.element.foreach { + case fpe: FlatPathElement ⇒ + if (siteMap.contains(fpe.element)) { + elements.append(fpe.copy()) + } + case npe: NestedPathElement ⇒ + val nested = makeLeanPathAcc(npe, siteMap) + if (nested.isDefined) { + elements.append(nested.get) + } + // For the case the element is a SubPath (should never happen but the compiler want it) + case _ ⇒ + } + + if (elements.nonEmpty) { + Some(NestedPathElement(elements, toProcess.elementType)) + } else { + None + } + } + + /** + * Takes `this` path and transforms it into a new [[Path]] where only those sites are contained + * that either use or define `obj`. + * + * @param obj Identifies the object of interest. That is, all definition and use sites of this + * object will be kept in the resulting lean path. `obj` should refer to a use site, + * most likely corresponding to an (implicit) `toString` call. + * @param stmts A list of look-up statements, i.e., a program / method description in which + * `obj` occurs. + * @return Returns a lean path of `this` path. That means, `this` instance will be stripped to + * contain only [[FlatPathElement]]s and [[NestedPathElement]]s that contain a + * definition or usage of `obj`. This includes the removal of [[NestedPathElement]]s + * not containing `obj`. In case `this` path does not contain `obj` at all, `None` will + * be returned. + * + * @note This function does not change the underlying `this` instance. Furthermore, all relevant + * elements for the lean path will be copied, i.e., `this` instance and the returned + * instance do not share any references. + */ + def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): Option[Path] = { + // Transform the list into a map to have a constant access time + val siteMap = Map(getAllDefAndUseSites(obj, stmts) map { s ⇒ (s, Unit) }: _*) + val leanPath = ListBuffer[SubPath]() + elements.foreach { + case fpe: FlatPathElement if siteMap.contains(fpe.element) ⇒ + leanPath.append(fpe) + case npe: NestedPathElement ⇒ + val leanedPath = makeLeanPathAcc(npe, siteMap) + if (leanedPath.isDefined) { + leanPath.append(leanedPath.get) + } + case _ ⇒ + } + + if (elements.isEmpty) { + None + } else { + Some(Path(leanPath.toList)) + } + } + +} From 7c717512a7e9bc63e6b5e144f2363f5802ba7a06 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 23 Nov 2018 17:48:31 +0100 Subject: [PATCH 035/316] Refined the NestedPathTypes. --- .../preprocessing/DefaultPathFinder.scala | 17 +++++++++----- .../preprocessing/Path.scala | 22 ++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 5af6b2644c..3ce535423c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -43,7 +43,7 @@ class DefaultPathFinder extends AbstractPathFinder { // Multiple start sites => We start within a conditional => Prepare for that if (startSites.size > 1) { - val outerNested = generateNestPathElement(startSites.size, NestedPathType.Conditional) + val outerNested = generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) numSplits.append(startSites.size) currSplitIndex.append(0) nestedElementsRef.append(outerNested) @@ -100,7 +100,7 @@ class DefaultPathFinder extends AbstractPathFinder { var ref: NestedPathElement = nestedElementsRef.head // Refine for conditionals ref.elementType match { - case Some(t) if t == NestedPathType.Conditional ⇒ + case Some(t) if t == NestedPathType.CondWithAlternative ⇒ ref = ref.element(currSplitIndex.head).asInstanceOf[NestedPathElement] case _ ⇒ } @@ -146,10 +146,17 @@ class DefaultPathFinder extends AbstractPathFinder { if (successors.length > 1 && !isLoopHeader) { val appendSite = if (numSplits.isEmpty) path else nestedElementsRef(currSplitIndex.head).element - val relevantNumSuccessors = if (isCondWithoutElse(popped, cfg)) - successors.size - 1 else successors.size + var relevantNumSuccessors = successors.size + var ifWithElse = true + + if (isCondWithoutElse(popped, cfg)) { + relevantNumSuccessors -= 1 + ifWithElse = false + } val outerNested = generateNestPathElement( - relevantNumSuccessors, NestedPathType.Conditional + relevantNumSuccessors, + if (ifWithElse) NestedPathType.CondWithAlternative else + NestedPathType.CondWithoutAlternative ) numSplits.prepend(relevantNumSuccessors) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 7fb8072f48..7a94b63d0a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -27,7 +27,27 @@ case class FlatPathElement(element: Int) extends SubPath * Identifies the nature of a nested path element. */ object NestedPathType extends Enumeration { - val Loop, Conditional = Value + + /** + * Used to mark any sort of loops. + */ + val Loop: NestedPathType.Value = Value + + /** + * Use this type to mark a conditional that has an alternative that is guaranteed to be + * executed. For instance, an `if` with an `else` block would fit this type, as would a `case` + * with a `default`. These are just examples for high-level languages. The concepts, however, + * can be applied to low-level format as well. + */ + val CondWithAlternative: NestedPathType.Value = Value + + /** + * Use this type to mark a conditional that is not necessarily executed. For instance, an `if` + * without an `else` (but possibly several `else if` fits this category. Again, this is to be + * mapped to low-level representations as well. + */ + val CondWithoutAlternative: NestedPathType.Value = Value + } /** From 9626d4ef0e46dc909eb568d36c2b222f33c61cca Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 25 Nov 2018 17:31:25 +0100 Subject: [PATCH 036/316] Implemented the path transformer along with classes that provide interpretation functionalities. --- .../StringConstancyLevel.java | 2 +- .../NewStringBuilderProcessor.scala | 355 ------------------ .../VirtualFunctionCallProcessor.scala | 191 ---------- .../AbstractExprProcessor.scala | 2 +- .../AbstractStringInterpreter.scala | 33 ++ .../interpretation/ArrayLoadInterpreter.scala | 50 +++ .../ArrayLoadProcessor.scala | 59 ++- .../BinaryExprInterpreter.scala | 49 +++ .../ExprHandler.scala | 139 +++---- .../interpretation/NewInterpreter.scala | 35 ++ .../NewStringBuilderProcessor.scala | 90 +++++ .../NonVirtualFunctionCallProcessor.scala | 2 +- .../NonVirtualMethodCallInterpreter.scala | 67 ++++ .../StringConstInterpreter.scala | 35 ++ .../StringConstProcessor.scala | 2 +- .../VirtualFunctionCallInterpreter.scala | 67 ++++ .../VirtualFunctionCallProcessor.scala | 178 +++++++++ .../preprocessing/DefaultPathFinder.scala | 4 +- .../preprocessing/Path.scala | 2 +- .../preprocessing/PathTransformer.scala | 120 ++++++ 20 files changed, 812 insertions(+), 670 deletions(-) delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/{expr_processing => interpretation}/AbstractExprProcessor.scala (98%) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/{expr_processing => interpretation}/ArrayLoadProcessor.scala (56%) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/{expr_processing => interpretation}/ExprHandler.scala (51%) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/{expr_processing => interpretation}/NonVirtualFunctionCallProcessor.scala (97%) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/{expr_processing => interpretation}/StringConstProcessor.scala (97%) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java index 3e168a3d47..c8831f47ff 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java @@ -3,7 +3,7 @@ /** * Java annotations do not work with Scala enums, such as - * {@link org.opalj.fpcf.properties.StringConstancyLevel}. Thus, this enum. + * {@link org.opalj.fpcf.string_definition.properties.StringConstancyLevel}. Thus, this enum. * * @author Patrick Mell */ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala deleted file mode 100644 index 1aba0644d2..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NewStringBuilderProcessor.scala +++ /dev/null @@ -1,355 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing - -import org.opalj.br.cfg.BasicBlock -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.tac.Stmt -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConcat -import org.opalj.fpcf.string_definition.properties.StringTreeCond -import org.opalj.fpcf.string_definition.properties.StringTreeConst -import org.opalj.fpcf.string_definition.properties.StringTreeOr -import org.opalj.fpcf.string_definition.properties.StringTreeRepetition -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.New -import org.opalj.tac.NonVirtualMethodCall -import org.opalj.tac.TACStmts -import org.opalj.tac.VirtualFunctionCall - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -/** - * - * @author Patrick Mell - */ -class NewStringBuilderProcessor( - private val exprHandler: ExprHandler -) extends AbstractExprProcessor { - - /** - * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will - * be returned). - * - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = { - assignment.expr match { - case _: New ⇒ - val uses = assignment.targetVar.usedBy.filter(!ignore.contains(_)).toArray.sorted - val (inits, nonInits) = getInitsAndNonInits(uses, stmts, cfg) - val initTreeNodes = ListBuffer[StringTree]() - val nonInitTreeNodes = mutable.Map[Int, ListBuffer[StringTree]]() - - inits.foreach { next ⇒ - val toProcess = stmts(next) match { - case init: NonVirtualMethodCall[V] if init.params.nonEmpty ⇒ - init.params.head.asVar.definedBy.toArray.sorted - case assignment: Assignment[V] ⇒ - val vfc = assignment.expr.asVirtualFunctionCall - var defs = vfc.receiver.asVar.definedBy - if (vfc.params.nonEmpty) { - vfc.params.head.asVar.definedBy.foreach(defs += _) - } - defs ++= assignment.targetVar.asVar.usedBy - defs.toArray.sorted - case _ ⇒ - Array() - } - val processed = if (toProcess.length == 1) { - val intermRes = exprHandler.processDefSite(toProcess.head) - if (intermRes.isDefined) intermRes else None - } else { - val children = toProcess.map(exprHandler.processDefSite). - filter(_.isDefined).map(_.get) - children.length match { - case 0 ⇒ None - case 1 ⇒ Some(children.head) - case _ ⇒ Some(StringTreeConcat(children.to[ListBuffer])) - } - } - if (processed.isDefined) { - initTreeNodes.append(processed.get) - } - } - - nonInits.foreach { next ⇒ - val subtree = exprHandler.concatDefSites(next) - if (subtree.isDefined) { - val key = next.min - if (nonInitTreeNodes.contains(key)) { - nonInitTreeNodes(key).append(subtree.get) - } else { - nonInitTreeNodes(key) = ListBuffer(subtree.get) - } - } - } - - if (initTreeNodes.isEmpty && nonInitTreeNodes.isEmpty) { - return None - } - - // Append nonInitTreeNodes to initTreeNodes (as children) - if (nonInitTreeNodes.nonEmpty) { - val toAppend = nonInitTreeNodes.size match { - // If there is only one element in the map use it; but first check the - // relation between that element and the init element - case 1 ⇒ - val postDomTree = cfg.postDominatorTree - if (initTreeNodes.nonEmpty && - !postDomTree.doesPostDominate(nonInits.head.head, inits.head)) { - // If not post-dominated, it is optional => Put it in a Cond - StringTreeCond(nonInitTreeNodes.head._2) - } else { - nonInitTreeNodes.head._2.head - } - // Otherwise, we need to build the proper tree, considering dominators - case _ ⇒ orderNonInitNodes(nonInitTreeNodes, cfg) - } - if (initTreeNodes.isEmpty) { - initTreeNodes.append(toAppend) - } else { - initTreeNodes.zipWithIndex.foreach { - case (rep: StringTreeRepetition, _) ⇒ rep.child = toAppend - // We cannot add to a constant element => slightly rearrange the tree - case (const: StringTreeConst, index) ⇒ - initTreeNodes(index) = StringTreeConcat(ListBuffer(const, toAppend)) - case (next, _) ⇒ next.children.append(toAppend) - } - } - } - - initTreeNodes.size match { - case 1 ⇒ Some(initTreeNodes.head) - case _ ⇒ Some(StringTreeOr(initTreeNodes)) - } - case _ ⇒ None - } - } - - /** - * This implementation does not change / implement the behavior of - * [[AbstractExprProcessor.processExpr]]. - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = super.processExpr(expr, stmts, cfg, ignore) - - /** - * - * @param useSites Not-supposed to contain already processed sites. Also, they should be in - * ascending order. - * @param stmts A list of statements (the one that was passed on to the `process`function of - * this class). - * @return - */ - private def getInitsAndNonInits( - useSites: Array[Int], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]] - ): (List[Int], List[List[Int]]) = { - val domTree = cfg.dominatorTree - val inits = ListBuffer[Int]() - var nonInits = ListBuffer[Int]() - - useSites.foreach { next ⇒ - stmts(next) match { - // Constructors are identified by the "init" method and assignments (ExprStmts, in - // contrast, point to non-constructor related calls) - case mc: NonVirtualMethodCall[V] if mc.name == "" ⇒ inits.append(next) - case _: Assignment[V] ⇒ - // Use dominator tree to determine whether init or noninit - if (domTree.doesDominate(inits.toArray, next)) { - nonInits.append(next) - } else { - inits.append(next) - } - case _ ⇒ nonInits.append(next) - } - } - // Sort in descending order to enable correct grouping in the next step - nonInits = nonInits.sorted.reverse - - // Next, group all non inits into lists depending on their basic block in the CFG; as the - // "belongs to parent" relationship is transitive, there are two approaches: 1) recursively - // check or 2) store grandchildren in a flat structure as well. Here, 2) is implemented as - // only references are stored which is not so expensive. However, this leads to the fact - // that we need to create a distinct map before returning (see declaration of uniqueLists - // below) - val blocks = mutable.LinkedHashMap[BasicBlock, ListBuffer[Int]]() - nonInits.foreach { next ⇒ - val nextBlock = cfg.bb(next) - val parentBlock = nextBlock.successors.filter { - case bb: BasicBlock ⇒ blocks.contains(bb) - case _ ⇒ false - } - if (parentBlock.nonEmpty) { - val list = blocks(parentBlock.head.asBasicBlock) - list.append(next) - blocks += (nextBlock → list) - } else { - blocks += (nextBlock → ListBuffer[Int](next)) - } - } - - // Make the list unique (as described above) and sort it in ascending order - val uniqueLists = blocks.values.toList.distinct.reverse - (inits.toList.sorted, uniqueLists.map(_.toList)) - } - - /** - * The relation of nodes is not obvious, i.e., one cannot generally say that two nodes, which, - * e.g., modify a [[StringBuilder]] object are concatenations or are to be mapped to a - * [[StringTreeOr]]. - *

- * This function uses the following algorithm to determine the relation of the given nodes: - *

    - *
  1. - * For each given node, compute the dominators (and store it in lists called - * ''dominatorLists'').
  2. - *
  3. - * Take all ''dominatorLists'' and union them (without duplicates) and sort this list in - * descending order. This list, called ''uniqueDomList'', is then used in the next step. - *
  4. - *
  5. - * Traverse ''uniqueDomList''. Here, we call the next element in that list ''nextDom''. - * One of the following cases will occur: - *
      - *
    1. - * Only one [[StringTree]] element in ''treeNodes'' has ''nextDom'' as a - * dominator. In this case, nothing is done as we cannot say anything about the - * relation to other elements in ''treeNodes''. - *
    2. - *
    3. - * At least two elements in ''treeNodes'' have ''nextDom'' as a dominator. In this - * case, these nodes are in a 'only-one-node-is-evaluated-relation' as it happens - * when the dominator of two nodes is an if condition, for example. Thus, these - * elements are put into a [[StringTreeOr]] element and then no longer considered - * for the computation. - *
    4. - *
    - *
  6. - *
  7. - * It might be that not all elements in ''treeNodes'' were processed by the second case of - * the previous step (normally, this should be only one element. These elements represent - * a concatenation relation. Thus, all [[StringTreeOr]] elements of the last step are then - * put into a [[StringTreeConcat]] element with the ones not processed yet. They are - * ordered in ascending order according to their index in the statement list to preserve - * the correct order. - *
  8. - *
- * - * @param treeNodes The nodes which are to be ordered. The key of the map refers to the index in - * the statement list. It might be that some operation (e.g., append) have two - * definition sites; In this case, pass the minimum index. The values of the - * map correspond to [[StringTree]] elements that resulted from the evaluation - * of the definition site(s). - * @param cfg The control flow graph. - * @return This function computes the correct relation of the given [[StringTree]] elements - * and returns this as a single tree element. - */ - private def orderNonInitNodes( - treeNodes: mutable.Map[Int, ListBuffer[StringTree]], - cfg: CFG[Stmt[V], TACStmts[V]] - ): StringTree = { - // TODO: Put this function in a different place (like DominatorTreeUtils) and generalize the procedure. - val domTree = cfg.dominatorTree - // For each list of nodes, get the dominators - val rootIndex = cfg.startBlock.startPC - val dominatorLists = mutable.Map[Int, List[Int]]() - for ((key, _) ← treeNodes) { - val dominators = ListBuffer[Int]() - var next = domTree.immediateDominators(key) - while (next != rootIndex) { - dominators.prepend(next) - next = domTree.immediateDominators(next) - } - dominators.prepend(rootIndex) - dominatorLists(key) = dominators.toList - } - - // Build a unique union of the dominators that is in descending order - var uniqueDomList = ListBuffer[Int]() - for ((_, value) ← dominatorLists) { - uniqueDomList.append(value: _*) - } - uniqueDomList = uniqueDomList.distinct.sorted.reverse - - val newChildren = ListBuffer[StringTree]() - uniqueDomList.foreach { nextDom ⇒ - // Find elements with the same dominator - val indicesWithSameDom = ListBuffer[Int]() - for ((key, value) ← dominatorLists) { - if (value.contains(nextDom)) { - indicesWithSameDom.append(key) - } - } - if (indicesWithSameDom.size > 1) { - val newOrElement = ListBuffer[StringTree]() - // Sort in order to have the correct order - indicesWithSameDom.sorted.foreach { nextIndex ⇒ - newOrElement.append(treeNodes(nextIndex).head) - dominatorLists.remove(nextIndex) - } - newChildren.append(StringTreeOr(newOrElement)) - } - } - - // If there are elements left, add them as well (they represent concatenations) - for ((key, _) ← dominatorLists) { - newChildren.append(treeNodes(key).head) - } - - if (newChildren.size == 1) { - newChildren.head - } else { - StringTreeConcat(newChildren) - } - } - -} - -object NewStringBuilderProcessor { - - /** - * Determines the definition site of the initialization of the base object that belongs to a - * ''toString'' call. - * - * @param toString The ''toString'' call of the object for which to get the initialization def - * site for. Make sure that the object is a subclass of - * [[AbstractStringBuilder]]. - * @param stmts A list of statements which will be used to lookup which one the initialization - * is. - * @return Returns the definition site of the base object of the call. If something goes wrong, - * e.g., no initialization is found, ''None'' is returned. - */ - def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { - // TODO: Check that we deal with an instance of AbstractStringBuilder - if (toString.name != "toString") { - return List() - } - - val defSites = ListBuffer[Int]() - val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.toArray: _*) - while (stack.nonEmpty) { - val next = stack.pop() - stmts(next) match { - case a: Assignment[V] ⇒ - a.expr match { - case _: New ⇒ - defSites.append(next) - case vfc: VirtualFunctionCall[V] ⇒ - stack.pushAll(vfc.receiver.asVar.definedBy.toArray) - } - case _ ⇒ - } - } - - defSites.sorted.toList - } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala deleted file mode 100644 index 0d71706258..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/VirtualFunctionCallProcessor.scala +++ /dev/null @@ -1,191 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing - -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.UnknownWordSymbol -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConcat -import org.opalj.fpcf.string_definition.properties.StringTreeCond -import org.opalj.fpcf.string_definition.properties.StringTreeElement -import org.opalj.fpcf.string_definition.properties.StringTreeConst -import org.opalj.tac.Assignment -import org.opalj.tac.BinaryExpr -import org.opalj.tac.Expr -import org.opalj.tac.NonVirtualFunctionCall -import org.opalj.tac.Stmt -import org.opalj.tac.StringConst -import org.opalj.tac.TACStmts -import org.opalj.tac.VirtualFunctionCall - -import scala.collection.mutable.ListBuffer - -/** - * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.VirtualFunctionCall]] - * expressions. - * Currently, [[VirtualFunctionCallProcessor]] (only) aims at processing calls of - * [[StringBuilder#append]]. - * - * @author Patrick Mell - */ -class VirtualFunctionCallProcessor( - private val exprHandler: ExprHandler, - private val cfg: CFG[Stmt[V], TACStmts[V]] -) extends AbstractExprProcessor { - - /** - * `expr` of `assignment`is required to be of type [[org.opalj.tac.VirtualFunctionCall]] - * (otherwise `None` will be returned). - * - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(assignment.expr, Some(assignment), stmts, ignore) - - /** - * @see [[AbstractExprProcessor.processExpr]]. - * - * @note For expressions, some information are not available that an [[Assignment]] captures. - * Nonetheless, as much information as possible is extracted from this implementation (but - * no use sites for `append` calls, for example). - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(expr, None, stmts, ignore) - - /** - * Wrapper function for processing assignments. - */ - private def process( - expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = { - expr match { - case vfc: VirtualFunctionCall[V] ⇒ - if (ExprHandler.isStringBuilderAppendCall(expr)) { - processAppendCall(expr, assignment, stmts, ignore) - } else if (ExprHandler.isStringBuilderToStringCall(expr)) { - processToStringCall(assignment, stmts, ignore) - } // A call to method which is not (yet) supported - else { - val ps = ExprHandler.classNameToPossibleString( - vfc.descriptor.returnType.toJavaClass.getSimpleName - ) - Some(StringTreeConst(StringConstancyInformation(DYNAMIC, ps))) - } - case _ ⇒ None - } - } - - /** - * Function for processing calls to [[StringBuilder#append]]. - */ - private def processAppendCall( - expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTreeElement] = { - val defSites = expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray.sorted - val appendValue = valueOfAppendCall(expr.asVirtualFunctionCall, stmts, ignore) - // Append has been processed before => do not compute again - if (appendValue.isEmpty) { - return None - } - - val leftSiblings = exprHandler.processDefSites(defSites) - // For assignments, we can take use sites into consideration as well - var rightSiblings: Option[StringTree] = None - if (assignment.isDefined) { - val useSites = assignment.get.targetVar.asVar.usedBy.toArray.sorted - rightSiblings = exprHandler.processDefSites(useSites) - } - - if (leftSiblings.isDefined || rightSiblings.isDefined) { - // Combine siblings and return - val concatElements = ListBuffer[StringTreeElement]() - if (leftSiblings.isDefined) { - concatElements.append(leftSiblings.get) - } - concatElements.append(appendValue.get) - if (rightSiblings.isDefined) { - concatElements.append(rightSiblings.get) - } - Some(StringTreeConcat(concatElements)) - } else { - Some(appendValue.get) - } - } - - /** - * Function for processing calls to [[StringBuilder.toString]]. Note that a value not equals to - * `None` can only be expected if `assignments` is defined. - */ - private def processToStringCall( - assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = { - if (assignment.isEmpty) { - return None - } - - val children = ListBuffer[StringTreeElement]() - val call = assignment.get.expr.asVirtualFunctionCall - val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) - defSites.foreach { - exprHandler.processDefSite(_) match { - case Some(subtree) ⇒ children.append(subtree) - case None ⇒ - } - } - - children.size match { - case 0 ⇒ None - case 1 ⇒ Some(children.head) - case _ ⇒ Some(StringTreeCond(children)) - } - } - - /** - * Determines the string value that was passed to a `StringBuilder#append` method. This function - * can process string constants as well as function calls as argument to append. - * - * @param call A function call of `StringBuilder#append`. Note that for all other methods an - * [[IllegalArgumentException]] will be thrown. - * @param stmts The surrounding context, e.g., the surrounding method. - * @return Returns a [[StringTreeConst]] with no children and the following value for - * [[StringConstancyInformation]]: For constants strings as arguments, this function - * returns the string value and the level - * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT]]. For - * function calls "*" (to indicate ''any value'') and - * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC]]. - */ - private def valueOfAppendCall( - call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTreeConst] = { - val defAssignment = call.params.head.asVar.definedBy.head - // The definition has been seen before => do not recompute - if (ignore.contains(defAssignment)) { - return None - } - - val assign = stmts(defAssignment).asAssignment - val sci = assign.expr match { - case _: NonVirtualFunctionCall[V] ⇒ - StringConstancyInformation(DYNAMIC, UnknownWordSymbol) - case StringConst(_, value) ⇒ - StringConstancyInformation(CONSTANT, value) - // Next case is for an append call as argument to append - case _: VirtualFunctionCall[V] ⇒ - processAssignment(assign, stmts, cfg).get.reduce() - case be: BinaryExpr[V] ⇒ - val possibleString = ExprHandler.classNameToPossibleString( - be.left.asVar.value.getClass.getSimpleName - ) - StringConstancyInformation(DYNAMIC, possibleString) - } - Some(StringTreeConst(sci)) - } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala similarity index 98% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala index da5110f81e..879b06109e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/AbstractExprProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing +package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala new file mode 100644 index 0000000000..d2c69d8936 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala @@ -0,0 +1,33 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * @param cfg The control flow graph that underlies the instruction to interpret. + * @param exprHandler In order to interpret an instruction, it might be necessary to interpret + * another instruction in the first place. `exprHandler` makes this possible. + * + * @author Patrick Mell + */ +abstract class AbstractStringInterpreter( + protected val cfg: CFG[Stmt[V], TACStmts[V]], + protected val exprHandler: ExprHandler, +) { + + type T <: Any + + /** + * + * @param instr The instruction that is to be interpreted. It is the responsibility of + * implementations to make sure that an instruction is properly and comprehensively + * evaluated. + * @return + */ + def interpret(instr: T): List[StringConstancyInformation] + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala new file mode 100644 index 0000000000..c0c0aa9d10 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala @@ -0,0 +1,50 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.tac.ArrayLoad +import org.opalj.tac.ArrayStore +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +import scala.collection.mutable.ListBuffer + +/** + * The `ArrayLoadInterpreter` is responsible for processing [[ArrayLoad]] expressions. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class ArrayLoadInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = ArrayLoad[V] + + /** + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + val stmts = cfg.code.instructions + val children = ListBuffer[StringConstancyInformation]() + // Loop over all possible array values + instr.arrayRef.asVar.definedBy.toArray.sorted.foreach { next ⇒ + val arrDecl = stmts(next) + val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + sortedArrDeclUses.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted + sortedDefs.map { exprHandler.processDefSite }.foreach { + children.appendAll(_) + } + } + } + + children.toList + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala similarity index 56% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala index 5ae91e9552..00bc9f4360 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ArrayLoadProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala @@ -1,19 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing +package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeElement -import org.opalj.fpcf.string_definition.properties.StringTreeOr -import org.opalj.tac.ArrayLoad -import org.opalj.tac.ArrayStore import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import scala.collection.mutable.ListBuffer - /** * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.ArrayLoad]] * expressions. @@ -56,31 +50,32 @@ class ArrayLoadProcessor( private def process( expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] ): Option[StringTree] = { - expr match { - case al: ArrayLoad[V] ⇒ - val children = ListBuffer[StringTreeElement]() - // Loop over all possible array values - al.arrayRef.asVar.definedBy.toArray.sorted.foreach { next ⇒ - val arrDecl = stmts(next) - val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted - sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int ⇒ - val sortedSDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - val arrValues = sortedSDefs.map { - exprHandler.processDefSite - }.filter(_.isDefined).map(_.get) - children.appendAll(arrValues) - } - } - - if (children.nonEmpty) { - Some(StringTreeOr(children)) - } else { - None - } - case _ ⇒ None - } + None + // expr match { + // case al: ArrayLoad[V] ⇒ + // val children = ListBuffer[StringTreeElement]() + // // Loop over all possible array values + // al.arrayRef.asVar.definedBy.toArray.sorted.foreach { next ⇒ + // val arrDecl = stmts(next) + // val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + // sortedArrDeclUses.filter { + // stmts(_).isInstanceOf[ArrayStore[V]] + // } foreach { f: Int ⇒ + // val sortedSDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted + // val arrValues = sortedSDefs.map { + // exprHandler.processDefSite + // }.filter(_.isDefined).map(_.get) + // children.appendAll(arrValues) + // } + // } + // + // if (children.nonEmpty) { + // Some(StringTreeOr(children)) + // } else { + // None + // } + // case _ ⇒ None + // } } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala new file mode 100644 index 0000000000..e8cd477a82 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala @@ -0,0 +1,49 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.tac.TACStmts +import org.opalj.br.cfg.CFG +import org.opalj.br.ComputationalTypeFloat +import org.opalj.br.ComputationalTypeInt +import org.opalj.tac.Stmt +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.tac.BinaryExpr + +/** + * The `BinaryExprInterpreter` is responsible for processing [[BinaryExpr]]ions. A list of currently + * supported binary expressions can be found in the documentation of [[interpret]]. + * + * @see [[AbstractStringInterpreter]] + * @author Patrick Mell + */ +class BinaryExprInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = BinaryExpr[V] + + /** + * Currently, this implementation supports the interpretation of the following binary + * expressions: + *
    + *
  • [[ComputationalTypeInt]] + *
  • [[ComputationalTypeFloat]]
  • + * + * To be more precise, that means that a list with one element will be returned. In all other + * cases, an empty list will be returned. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + instr.cTpe match { + case ComputationalTypeInt ⇒ + List(StringConstancyInformation(StringConstancyLevel.DYNAMIC, "[AnIntegerValue]")) + case ComputationalTypeFloat ⇒ + List(StringConstancyInformation(StringConstancyLevel.DYNAMIC, "[AFloatValue]")) + case _ ⇒ List() + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala similarity index 51% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala index e2488a04bb..ed6e755c04 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala @@ -1,21 +1,16 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing +package org.opalj.fpcf.analyses.string_definition.interpretation -import org.opalj.br.Method -import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConcat -import org.opalj.fpcf.string_definition.properties.StringTreeOr -import org.opalj.fpcf.string_definition.properties.StringTreeRepetition +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment +import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr import org.opalj.tac.ExprStmt import org.opalj.tac.New -import org.opalj.tac.NonVirtualFunctionCall -import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.StringConst import org.opalj.tac.TACStmts @@ -28,62 +23,54 @@ import scala.collection.mutable.ListBuffer * which value(s) a string read operation might have. These expressions usually come from the * definitions sites of the variable of interest. * - * @param p The project associated with the analysis. - * @param m The [[Method]] in which the read statement of the string variable of interest occurred. + * @param cfg The control flow graph that underlies the program / method in which the expressions of + * interest reside. * @author Patrick Mell */ -class ExprHandler(p: SomeProject, m: Method) { +class ExprHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { - private val tacProvider = p.get(SimpleTACAIKey) - private val ctxStmts = tacProvider(m).stmts + private val stmts = cfg.code.instructions private val processedDefSites = ListBuffer[Int]() - private val cfg = tacProvider(m).cfg /** - * Processes a given definition site. That is, this function determines the - * [[StringTree]] of a string definition. + * Processes a given definition site. That is, this function determines the interpretation of + * the specified instruction. * * @param defSite The definition site to process. Make sure that (1) the value is >= 0, (2) it - * actually exists, and (3) contains an Assignment whose expression is of a type - * that is supported by a sub-class of [[AbstractExprProcessor]]. - * @return Returns a StringTee that describes the definition at the specified site. In case the - * rules listed above or the ones of the different processors are not met `None` will be - * returned. + * actually exists, and (3) can be processed by one of the subclasses of + * [[AbstractStringInterpreter]] (in case (3) is violated, an + * [[IllegalArgumentException]] will be thrown. + * @return Returns a list of interpretations in the form of [[StringConstancyInformation]]. In + * case the rules listed above or the ones of the different processors are not met, an + * empty list will be returned. */ - def processDefSite(defSite: Int): Option[StringTree] = { + def processDefSite(defSite: Int): List[StringConstancyInformation] = { if (defSite < 0 || processedDefSites.contains(defSite)) { - return None + return List() } processedDefSites.append(defSite) - // Determine whether to process an assignment or an expression - val expr = ctxStmts(defSite) match { - case a: Assignment[V] ⇒ a.expr - case e: ExprStmt[V] ⇒ e.expr - case _ ⇒ return None - } - val exprProcessor: AbstractExprProcessor = expr match { - case _: ArrayLoad[V] ⇒ new ArrayLoadProcessor(this) - case _: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallProcessor(this, cfg) - case _: New ⇒ new NewStringBuilderProcessor(this) - case _: NonVirtualFunctionCall[V] ⇒ new NonVirtualFunctionCallProcessor() - case _: StringConst ⇒ new StringConstProcessor() - case _ ⇒ throw new IllegalArgumentException( - s"cannot process expression $expr" - ) - } + stmts(defSite) match { + case Assignment(_, _, expr) if expr.isInstanceOf[StringConst] => + new StringConstInterpreter(cfg, this).interpret(expr.asStringConst) + case Assignment(_, _, expr) if expr.isInstanceOf[ArrayLoad[V]] => + new ArrayLoadInterpreter(cfg, this).interpret(expr.asArrayLoad) + case Assignment(_, _, expr) if expr.isInstanceOf[New] => + new NewInterpreter(cfg, this).interpret(expr.asNew) + case Assignment(_, _, expr) if expr.isInstanceOf[VirtualFunctionCall[V]] => + new VirtualFunctionCallInterpreter(cfg, this).interpret(expr.asVirtualFunctionCall) + case Assignment(_, _, expr) if expr.isInstanceOf[BinaryExpr[V]] => + new BinaryExprInterpreter(cfg, this).interpret(expr.asBinaryExpr) + case ExprStmt(_, expr) => + expr match { + case vfc: VirtualFunctionCall[V] => + new VirtualFunctionCallInterpreter(cfg, this).interpret(vfc) + case _ => List() + } + case nvmc: NonVirtualMethodCall[V] => + new NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) + case _ => List() - val subtree = ctxStmts(defSite) match { - case a: Assignment[V] ⇒ - exprProcessor.processAssignment(a, ctxStmts, cfg, processedDefSites.toList) - case _ ⇒ - exprProcessor.processExpr(expr, ctxStmts, cfg, processedDefSites.toList) - } - - if (subtree.isDefined && ExprHandler.isWithinLoop(defSite, cfg)) { - Some(StringTreeRepetition(subtree.get, None)) - } else { - subtree } } @@ -94,45 +81,27 @@ class ExprHandler(p: SomeProject, m: Method) { * [[ExprHandler.processDefSite]] apply. * * @param defSites The definition sites to process. - * @return Returns a [[StringTree]]. In contrast to [[ExprHandler.processDefSite]] this function - * takes into consideration only those values from `processDefSite` that are not `None`. - * Furthermore, this function assumes that different definition sites originate from - * control flow statements; thus, this function returns a tree with a - * [[StringTreeOr]] as root and each definition site as a child. + * @return Returns a list of lists of [[StringConstancyInformation]]. Note that this function + * preserves the order of the given `defSites`, i.e., the first element in the result + * list corresponds to the first element in `defSites` and so on. If a site could not be + * processed, the list for that site will be the empty list. */ - def processDefSites(defSites: Array[Int]): Option[StringTree] = + def processDefSites(defSites: Array[Int]): List[List[StringConstancyInformation]] = defSites.length match { - case 0 ⇒ None - case 1 ⇒ processDefSite(defSites.head) - case _ ⇒ - val processedSites = defSites.filter(_ >= 0).sorted.map(processDefSite) - Some(StringTreeOr( - processedSites.filter(_.isDefined).map(_.get).to[ListBuffer] - )) + case 0 ⇒ List() + case 1 ⇒ List(processDefSite(defSites.head)) + case _ ⇒ defSites.filter(_ >= 0).map(processDefSite).toList } /** - * concatDefSites takes the given definition sites, processes them from the first to the last - * element and chains the resulting trees together. That means, a - * [[StringTreeConcat]] element is returned with one child for each def site in `defSites`. - * - * @param defSites The definition sites to concat / process. - * @return Returns either a [[StringTree]] or `None` in case `defSites` is empty (or does not - * contain processable def sites). + * The [[ExprHandler]] keeps an internal state for correct and faster processing. As long as a + * single object within a CFG is analyzed, there is no need to reset the state. However, when + * analyzing a second object (even the same object) it is necessary to call `reset` to reset the + * internal state. Otherwise, incorrect results will be produced. + * (Alternatively, you could instantiate another [[ExprHandler]] instance.) */ - def concatDefSites(defSites: List[Int]): Option[StringTree] = { - if (defSites.isEmpty) { - return None - } - - val children = defSites.sorted.map(processDefSite).filter(_.isDefined).map(_.get) - if (children.isEmpty) { - None - } else if (children.size == 1) { - Some(children.head) - } else { - Some(StringTreeConcat(children.to[ListBuffer])) - } + def reset(): Unit = { + processedDefSites.clear() } } @@ -148,7 +117,7 @@ object ExprHandler { /** * @see [[ExprHandler]] */ - def apply(p: SomeProject, m: Method): ExprHandler = new ExprHandler(p, m) + def apply(cfg: CFG[Stmt[V], TACStmts[V]]): ExprHandler = new ExprHandler(cfg) /** * Checks whether the given definition site is within a loop. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala new file mode 100644 index 0000000000..ede64b2673 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.tac.TACStmts +import org.opalj.tac.Stmt +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.tac.New + +/** + * The `NewInterpreter` is responsible for processing [[New]] expressions. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class NewInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = New + + /** + * [[New]] expressions do not carry any relevant information in this context (as the initial + * values are not set in a [[New]] expressions but, e.g., in + * [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, thus implementation always returns an + * empty list. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = List() + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala new file mode 100644 index 0000000000..7674277723 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala @@ -0,0 +1,90 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.tac.Stmt +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.tac.Assignment +import org.opalj.tac.Expr +import org.opalj.tac.New +import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualFunctionCall + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +/** + * + * @author Patrick Mell + */ +class NewStringBuilderProcessor( + private val exprHandler: ExprHandler +) extends AbstractExprProcessor { + + /** + * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will + * be returned). + * + * @see [[AbstractExprProcessor.processAssignment]] + */ + override def processAssignment( + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() + ): Option[StringTree] = { + assignment.expr match { + case _ ⇒ None + } + } + + /** + * This implementation does not change / implement the behavior of + * [[AbstractExprProcessor.processExpr]]. + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() + ): Option[StringTree] = super.processExpr(expr, stmts, cfg, ignore) + +} + +object NewStringBuilderProcessor { + + /** + * Determines the definition site of the initialization of the base object that belongs to a + * ''toString'' call. + * + * @param toString The ''toString'' call of the object for which to get the initialization def + * site for. Make sure that the object is a subclass of + * [[AbstractStringBuilder]]. + * @param stmts A list of statements which will be used to lookup which one the initialization + * is. + * @return Returns the definition site of the base object of the call. If something goes wrong, + * e.g., no initialization is found, ''None'' is returned. + */ + def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { + // TODO: Check that we deal with an instance of AbstractStringBuilder + if (toString.name != "toString") { + return List() + } + + val defSites = ListBuffer[Int]() + val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.toArray: _*) + while (stack.nonEmpty) { + val next = stack.pop() + stmts(next) match { + case a: Assignment[V] ⇒ + a.expr match { + case _: New ⇒ + defSites.append(next) + case vfc: VirtualFunctionCall[V] ⇒ + stack.pushAll(vfc.receiver.asVar.definedBy.toArray) + } + case _ ⇒ + } + } + + defSites.sorted.toList + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala similarity index 97% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala index 2822225d59..ac9b667fc2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/NonVirtualFunctionCallProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing +package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala new file mode 100644 index 0000000000..40394b2211 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala @@ -0,0 +1,67 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +import scala.collection.mutable.ListBuffer + +/** + * The `NonVirtualMethodCallInterpreter` is responsible for processing [[NonVirtualMethodCall]]s. + * For supported method calls, see the documentation of the `interpret` function. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class NonVirtualMethodCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = NonVirtualMethodCall[V] + + /** + * Currently, this function supports the interpretation of the following non virtual methods: + *
      + *
    • + * `<init>`, when initializing an object (for this case, currently zero constructor or + * one constructor parameter are supported; if more params are available, only the very first + * one is interpreted). + *
    • + *
    + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { + instr.name match { + case "" ⇒ interpretInit(instr) + case _ ⇒ List() + } + } + + /** + * Processes an `<init>` method call. If it has no parameters, an empty list will be + * returned. Otherwise, only the very first parameter will be evaluated and its result returned + * (this is reasonable as both, [[StringBuffer]] and [[StringBuilder]], have only constructors + * with <= 0 arguments and only these are currently interpreted). + */ + private def interpretInit(init: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { + init.params.size match { + case 0 ⇒ + List() + //List(StringConstancyInformation(StringConstancyLevel.CONSTANT, "")) + case _ ⇒ + val scis = ListBuffer[StringConstancyInformation]() + init.params.head.asVar.definedBy.foreach { ds ⇒ + scis.append(exprHandler.processDefSite(ds): _*) + } + scis.toList + } + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala new file mode 100644 index 0000000000..bbf354b9ae --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.tac.TACStmts +import org.opalj.tac.Stmt +import org.opalj.tac.StringConst + +/** + * The `StringConstInterpreter` is responsible for processing [[StringConst]]s. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class StringConstInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = StringConst + + /** + * The interpretation of a [[StringConst]] always results in a list with one + * [[StringConstancyInformation]] element. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + List(StringConstancyInformation(StringConstancyLevel.CONSTANT, instr.value)) + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala similarity index 97% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala index 63ed4dd6c0..637cb2ac09 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/expr_processing/StringConstProcessor.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.expr_processing +package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala new file mode 100644 index 0000000000..aa0219b6d2 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -0,0 +1,67 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.tac.VirtualFunctionCall + +/** + * The `VirtualFunctionCallInterpreter` is responsible for processing [[VirtualFunctionCall]]s. + * The list of currently supported function calls can be seen in the documentation of + * [[interpret]]. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class VirtualFunctionCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = VirtualFunctionCall[V] + + /** + * Currently, this implementation supports the interpretation of the following function calls: + *
      + *
    • `append`: Calls to the `append` function of [[StringBuilder]] and [[StringBuffer]].
    • + *
    • + * `toString`: Calls to the `append` function of [[StringBuilder]] and [[StringBuffer]]. As + * a `toString` call does not change the state of such an object, an empty list will be + * returned. + *
    • + *
    + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + instr.name match { + case "append" ⇒ interpretAppendCall(instr) + case "toString" ⇒ interpretToStringCall(instr) + case _ ⇒ List() + } + } + + /** + * Function for processing calls to [[StringBuilder#append]] or [[StringBuffer#append]]. Note + * that this function assumes that the given `appendCall` is such a function call! Otherwise, + * the expected behavior cannot be guaranteed. + */ + private def interpretAppendCall( + appendCall: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = + exprHandler.processDefSite(appendCall.params.head.asVar.definedBy.head) + + /** + * Function for processing calls to [[StringBuilder#toString]] or [[StringBuffer#toString]]. + * Note that this function assumes that the given `toString` is such a function call! Otherwise, + * the expected behavior cannot be guaranteed. + */ + private def interpretToStringCall( + appendCall: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = List() + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala new file mode 100644 index 0000000000..6399e58260 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala @@ -0,0 +1,178 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.tac.Assignment +import org.opalj.tac.Expr +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.VirtualFunctionCall]] + * expressions. + * Currently, [[VirtualFunctionCallProcessor]] (only) aims at processing calls of + * [[StringBuilder#append]]. + * + * @author Patrick Mell + */ +class VirtualFunctionCallProcessor( + private val exprHandler: ExprHandler, + private val cfg: CFG[Stmt[V], TACStmts[V]] +) extends AbstractExprProcessor { + + /** + * `expr` of `assignment`is required to be of type [[org.opalj.tac.VirtualFunctionCall]] + * (otherwise `None` will be returned). + * + * @see [[AbstractExprProcessor.processAssignment]] + */ + override def processAssignment( + assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() + ): Option[StringTree] = process(assignment.expr, Some(assignment), stmts, ignore) + + /** + * @see [[AbstractExprProcessor.processExpr]]. + * + * @note For expressions, some information are not available that an [[Assignment]] captures. + * Nonetheless, as much information as possible is extracted from this implementation (but + * no use sites for `append` calls, for example). + */ + override def processExpr( + expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], + ignore: List[Int] = List[Int]() + ): Option[StringTree] = process(expr, None, stmts, ignore) + + /** + * Wrapper function for processing assignments. + */ + private def process( + expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] + ): Option[StringTree] = { + None + // expr match { + // case vfc: VirtualFunctionCall[V] ⇒ + // if (ExprHandler.isStringBuilderAppendCall(expr)) { + // processAppendCall(expr, assignment, stmts, ignore) + // } else if (ExprHandler.isStringBuilderToStringCall(expr)) { + // processToStringCall(assignment, stmts, ignore) + // } // A call to method which is not (yet) supported + // else { + // val ps = ExprHandler.classNameToPossibleString( + // vfc.descriptor.returnType.toJavaClass.getSimpleName + // ) + // Some(StringTreeConst(StringConstancyInformation(DYNAMIC, ps))) + // } + // case _ ⇒ None + // } + } + + /** + * Function for processing calls to [[StringBuilder#append]]. + */ + // private def processAppendCall( + // expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] + // ): Option[StringTreeElement] = { + // val defSites = expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray.sorted + // val appendValue = valueOfAppendCall(expr.asVirtualFunctionCall, stmts, ignore) + // // Append has been processed before => do not compute again + // if (appendValue.isEmpty) { + // return None + // } + // + // val leftSiblings = exprHandler.processDefSites(defSites) + // // For assignments, we can take use sites into consideration as well + // var rightSiblings: Option[StringTree] = None + // if (assignment.isDefined) { + // val useSites = assignment.get.targetVar.asVar.usedBy.toArray.sorted + // rightSiblings = exprHandler.processDefSites(useSites) + // } + // + // if (leftSiblings.isDefined || rightSiblings.isDefined) { + // // Combine siblings and return + // val concatElements = ListBuffer[StringTreeElement]() + // if (leftSiblings.isDefined) { + // concatElements.append(leftSiblings.get) + // } + // concatElements.append(appendValue.get) + // if (rightSiblings.isDefined) { + // concatElements.append(rightSiblings.get) + // } + // Some(StringTreeConcat(concatElements)) + // } else { + // Some(appendValue.get) + // } + // } + + /** + * Function for processing calls to [[StringBuilder.toString]]. Note that a value not equals to + * `None` can only be expected if `assignments` is defined. + */ + // private def processToStringCall( + // assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] + // ): Option[StringTree] = { + // if (assignment.isEmpty) { + // return None + // } + // + // val children = ListBuffer[StringTreeElement]() + // val call = assignment.get.expr.asVirtualFunctionCall + // val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) + // defSites.foreach { + // exprHandler.processDefSite(_) match { + // case Some(subtree) ⇒ children.append(subtree) + // case None ⇒ + // } + // } + // + // children.size match { + // case 0 ⇒ None + // case 1 ⇒ Some(children.head) + // case _ ⇒ Some(StringTreeCond(children)) + // } + // } + + /** + * Determines the string value that was passed to a `StringBuilder#append` method. This function + * can process string constants as well as function calls as argument to append. + * + * @param call A function call of `StringBuilder#append`. Note that for all other methods an + * [[IllegalArgumentException]] will be thrown. + * @param stmts The surrounding context, e.g., the surrounding method. + * @return Returns a [[org.opalj.fpcf.string_definition.properties.StringTreeConst]] with no children and the following value for + * [[org.opalj.fpcf.string_definition.properties.StringConstancyInformation]]: For constants strings as arguments, this function + * returns the string value and the level + * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT]]. For + * function calls "*" (to indicate ''any value'') and + * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC]]. + */ + // private def valueOfAppendCall( + // call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] + // ): Option[StringTreeConst] = { + // val defAssignment = call.params.head.asVar.definedBy.head + // // The definition has been seen before => do not recompute + // if (ignore.contains(defAssignment)) { + // return None + // } + // + // val assign = stmts(defAssignment).asAssignment + // val sci = assign.expr match { + // case _: NonVirtualFunctionCall[V] ⇒ + // StringConstancyInformation(DYNAMIC, UnknownWordSymbol) + // case StringConst(_, value) ⇒ + // StringConstancyInformation(CONSTANT, value) + // // Next case is for an append call as argument to append + // case _: VirtualFunctionCall[V] ⇒ + // processAssignment(assign, stmts, cfg).get.reduce() + // case be: BinaryExpr[V] ⇒ + // val possibleString = ExprHandler.classNameToPossibleString( + // be.left.asVar.value.getClass.getSimpleName + // ) + // StringConstancyInformation(DYNAMIC, possibleString) + // } + // Some(StringTreeConst(sci)) + // } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 3ce535423c..fb29a4f3b4 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -73,7 +73,7 @@ class DefaultPathFinder extends AbstractPathFinder { numSplits.prepend(1) currSplitIndex.prepend(0) - val outer = generateNestPathElement(0, NestedPathType.Loop) + val outer = generateNestPathElement(0, NestedPathType.Repetition) outer.element.append(toAppend) nestedElementsRef.prepend(outer) path.append(outer) @@ -83,7 +83,7 @@ class DefaultPathFinder extends AbstractPathFinder { else if (isLoopEnding) { val loopElement = nestedElementsRef.find { _.elementType match { - case Some(et) ⇒ et == NestedPathType.Loop + case Some(et) ⇒ et == NestedPathType.Repetition case _ ⇒ false } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 7a94b63d0a..85ef2f61e1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -31,7 +31,7 @@ object NestedPathType extends Enumeration { /** * Used to mark any sort of loops. */ - val Loop: NestedPathType.Value = Value + val Repetition: NestedPathType.Value = Value /** * Use this type to mark a conditional that has an alternative that is guaranteed to be diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala new file mode 100644 index 0000000000..ae9272eaeb --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -0,0 +1,120 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.preprocessing + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.analyses.string_definition.interpretation.ExprHandler +import org.opalj.fpcf.string_definition.properties.StringTree +import org.opalj.fpcf.string_definition.properties.StringTreeConcat +import org.opalj.fpcf.string_definition.properties.StringTreeCond +import org.opalj.fpcf.string_definition.properties.StringTreeConst +import org.opalj.fpcf.string_definition.properties.StringTreeOr +import org.opalj.fpcf.string_definition.properties.StringTreeRepetition +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +import scala.collection.mutable.ListBuffer + +/** + * [[PathTransformer]] is responsible for transforming a [[Path]] into another representation, such + * as [[StringTree]]s for example. + * An instance can handle several consecutive transformations of different paths as long as they + * refer to the underlying control flow graph. If this is no longer the case, create a new instance + * of this class with the corresponding (new) `cfg?`. + * + * @param cfg Objects of this class require a control flow graph that is used for transformations. + * + * @author Patrick Mell + */ +class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { + + private val exprHandler = ExprHandler(cfg) + + /** + * Accumulator function for transforming a path into a StringTree element. + */ + private def pathToTreeAcc(subpath: SubPath): Option[StringTree] = { + subpath match { + case fpe: FlatPathElement ⇒ + val sciList = exprHandler.processDefSite(fpe.element) + sciList.length match { + case 0 ⇒ None + case 1 ⇒ Some(StringTreeConst(sciList.head)) + case _ ⇒ + val treeElements = ListBuffer[StringTree]() + treeElements.appendAll(sciList.map(StringTreeConst).to[ListBuffer]) + Some(StringTreeConcat(treeElements)) + } + case npe: NestedPathElement ⇒ + if (npe.elementType.isDefined) { + npe.elementType.get match { + case NestedPathType.Repetition ⇒ + val processedSubPath = pathToStringTree( + Path(npe.element.toList), resetExprHandler = false + ) + if (processedSubPath.isDefined) { + Some(StringTreeRepetition(processedSubPath.get)) + } else { + None + } + case _ ⇒ + val processedSubPaths = npe.element.map( + pathToTreeAcc + ).filter(_.isDefined).map(_.get) + npe.elementType.get match { + case NestedPathType.CondWithAlternative ⇒ + Some(StringTreeOr(processedSubPaths)) + case NestedPathType.CondWithoutAlternative ⇒ + Some(StringTreeCond(processedSubPaths)) + case _ ⇒ None + } + } + } else { + npe.element.size match { + case 0 ⇒ None + case 1 ⇒ pathToTreeAcc(npe.element.head) + case _ ⇒ Some(StringTreeConcat( + npe.element.map(pathToTreeAcc).filter(_.isDefined).map(_.get) + )) + } + } + case _ ⇒ None + } + } + + /** + * Takes a [[Path]] and transforms it into a [[StringTree]]. This implies an interpretation of + * how to handle methods called on the object of interest (like `append`). + * + * @param path The path element to be transformed. + * @param resetExprHandler Whether to reset the underlying [[ExprHandler]] or not. When calling + * this function from outside, the default value should do fine in most + * of the cases. For further information, see [[ExprHandler.reset]]. + * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed + * [[StringTree]] will be returned. Note that all elements of the tree will be defined, + * i.e., if `path` contains sites that could not be processed (successfully), they will + * not occur in the tree. + */ + def pathToStringTree(path: Path, resetExprHandler: Boolean = true): Option[StringTree] = { + val tree = path.elements.size match { + case 0 ⇒ None + case 1 ⇒ pathToTreeAcc(path.elements.head) + case _ ⇒ + val concatElement = Some(StringTreeConcat( + path.elements.map(pathToTreeAcc).filter(_.isDefined).map(_.get).to[ListBuffer] + )) + // It might be that concat has only one child (because some interpreters might have + // returned an empty list => In case of one child, return only that one + if (concatElement.isDefined && concatElement.get.children.size == 1) { + Some(concatElement.get.children.head) + } else { + concatElement + } + } + if (resetExprHandler) { + exprHandler.reset() + } + tree + } + +} From bf325ee9ffcb4576a8a2c1b2675cf368801dbf15 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 11:17:01 +0100 Subject: [PATCH 037/316] The current interpreters were not able to properly handle `append` and `toString` calls (which they do now). --- .../VirtualFunctionCallInterpreter.scala | 54 +++++++++++++++++-- .../preprocessing/Path.scala | 24 ++++++--- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index aa0219b6d2..1e13da2318 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -6,8 +6,11 @@ import org.opalj.tac.TACStmts import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.tac.VirtualFunctionCall +import scala.collection.mutable.ListBuffer + /** * The `VirtualFunctionCallInterpreter` is responsible for processing [[VirtualFunctionCall]]s. * The list of currently supported function calls can be seen in the documentation of @@ -52,8 +55,50 @@ class VirtualFunctionCallInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = - exprHandler.processDefSite(appendCall.params.head.asVar.definedBy.head) + ): List[StringConstancyInformation] = { + val scis = ListBuffer[StringConstancyInformation]() + + val receiverValue = receiverValueOfAppendCall(appendCall) + if (receiverValue.nonEmpty) { + scis.appendAll(receiverValue) + } + + val appendValue = valueOfAppendCall(appendCall) + if (appendValue.isDefined) { + scis.append(appendValue.get) + } + + scis.toList + } + + /** + * This function determines the current value of the receiver object of an `append` call. + */ + private def receiverValueOfAppendCall( + call: VirtualFunctionCall[V] + ): Option[StringConstancyInformation] = + exprHandler.processDefSite(call.receiver.asVar.definedBy.head).headOption + + /** + * Determines the (string) value that was passed to a `String{Builder, Buffer}#append` method. + * This function can process string constants as well as function calls as argument to append. + */ + private def valueOfAppendCall( + call: VirtualFunctionCall[V] + ): Option[StringConstancyInformation] = { + val value = exprHandler.processDefSite(call.params.head.asVar.definedBy.head) + // It might be necessary to merge the value of the receiver and of the parameter together + value.size match { + case 0 ⇒ None + case 1 ⇒ value.headOption + case _ ⇒ Some(StringConstancyInformation( + StringConstancyLevel.determineForConcat( + value.head.constancyLevel, value(1).constancyLevel + ), + value.head.possibleStrings + value(1).possibleStrings + )) + } + } /** * Function for processing calls to [[StringBuilder#toString]] or [[StringBuffer#toString]]. @@ -61,7 +106,8 @@ class VirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretToStringCall( - appendCall: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = List() + call: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = + exprHandler.processDefSite(call.receiver.asVar.definedBy.head) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 85ef2f61e1..d829ffa64b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -2,10 +2,14 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.tac.Assignment import org.opalj.tac.DUVar +import org.opalj.tac.New import org.opalj.tac.Stmt +import org.opalj.tac.VirtualFunctionCall import org.opalj.value.ValueInformation +import scala.collection.mutable import scala.collection.mutable.ListBuffer /** @@ -76,12 +80,20 @@ case class Path(elements: List[SubPath]) { obj: DUVar[ValueInformation], stmts: Array[Stmt[V]] ): List[Int] = { val defAndUses = ListBuffer[Int]() - - obj.definedBy.foreach { d ⇒ - val defSites = stmts(d).asAssignment.expr.asVirtualFunctionCall.receiver.asVar.definedBy - defSites.foreach { innerDS ⇒ - defAndUses.append(innerDS) - defAndUses.append(stmts(innerDS).asAssignment.targetVar.usedBy.toArray.toList: _*) + val stack = mutable.Stack[Int](obj.definedBy.toArray: _*) + + while (stack.nonEmpty) { + val popped = stack.pop() + if (!defAndUses.contains(popped)) { + defAndUses.append(popped) + + stmts(popped) match { + case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ + stack.pushAll(a.expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray) + case a: Assignment[V] if a.expr.isInstanceOf[New] ⇒ + stack.pushAll(a.targetVar.usedBy.toArray) + case _ ⇒ + } } } From 90aa496ec83083b27b498642a00713561c7dee2b Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 11:55:35 +0100 Subject: [PATCH 038/316] The `getAllDefAndUseSites` function had to be refined. --- .../fpcf/analyses/string_definition/preprocessing/Path.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index d829ffa64b..6aa3e9d94d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -90,6 +90,8 @@ case class Path(elements: List[SubPath]) { stmts(popped) match { case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ stack.pushAll(a.expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray) + // TODO: Does the following line add too much (in some cases)??? + stack.pushAll(a.targetVar.asVar.usedBy.toArray) case a: Assignment[V] if a.expr.isInstanceOf[New] ⇒ stack.pushAll(a.targetVar.usedBy.toArray) case _ ⇒ @@ -97,7 +99,7 @@ case class Path(elements: List[SubPath]) { } } - defAndUses.toList + defAndUses.toList.sorted } /** From fdb4c0b5c7ea96769194721766681091ba26fbc1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 13:12:35 +0100 Subject: [PATCH 039/316] Added a NonVirtualMethodCallInterpreter. --- .../interpretation/ExprHandler.scala | 5 +++ .../NonVirtualFunctionCallInterpreter.scala | 40 +++++++++++++++++++ .../NonVirtualMethodCallInterpreter.scala | 1 + 3 files changed, 46 insertions(+) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala index ed6e755c04..e7467e6997 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala @@ -10,6 +10,7 @@ import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr import org.opalj.tac.ExprStmt import org.opalj.tac.New +import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.StringConst @@ -61,6 +62,10 @@ class ExprHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { new VirtualFunctionCallInterpreter(cfg, this).interpret(expr.asVirtualFunctionCall) case Assignment(_, _, expr) if expr.isInstanceOf[BinaryExpr[V]] => new BinaryExprInterpreter(cfg, this).interpret(expr.asBinaryExpr) + case Assignment(_, _, expr) if expr.isInstanceOf[NonVirtualFunctionCall[V]] => + new NonVirtualFunctionCallInterpreter( + cfg, this + ).interpret(expr.asNonVirtualFunctionCall) case ExprStmt(_, expr) => expr match { case vfc: VirtualFunctionCall[V] => diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala new file mode 100644 index 0000000000..1d3e97283a --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala @@ -0,0 +1,40 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * The `NonVirtualFunctionCallInterpreter` is responsible for processing + * [[NonVirtualFunctionCall]]s. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class NonVirtualFunctionCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: ExprHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = NonVirtualFunctionCall[V] + + /** + * Currently, [[NonVirtualFunctionCall]] are not supported. Thus, this function always returns a + * list with a single element consisting of [[StringConstancyLevel.DYNAMIC]] and + * [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, StringConstancyInformation.UnknownWordSymbol + )) + } + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala index 40394b2211..2f822ba5c7 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala @@ -34,6 +34,7 @@ class NonVirtualMethodCallInterpreter( * one is interpreted). * *
+ * For all other calls, an empty list will be returned at the moment. * * @see [[AbstractStringInterpreter.interpret]] */ From eea69baa19e747f5fd66650ead57807f0990deed Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 14:33:59 +0100 Subject: [PATCH 040/316] The pathToTreeAcc function of PathTransformer had a bug. --- .../string_definition/preprocessing/PathTransformer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index ae9272eaeb..38a07ff672 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -43,7 +43,7 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { case _ ⇒ val treeElements = ListBuffer[StringTree]() treeElements.appendAll(sciList.map(StringTreeConst).to[ListBuffer]) - Some(StringTreeConcat(treeElements)) + Some(StringTreeOr(treeElements)) } case npe: NestedPathElement ⇒ if (npe.elementType.isDefined) { From ddcc724e04116b949ddab29f377812d5212d7518 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 15:30:17 +0100 Subject: [PATCH 041/316] Had to refine the pathToTreeAcc function along with the interpretAppendCall function to correctly distinguish Concats and Ors --- .../AbstractStringInterpreter.scala | 6 ++++- .../VirtualFunctionCallInterpreter.scala | 27 ++++++++++--------- .../preprocessing/PathTransformer.scala | 11 +++++--- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala index d2c69d8936..9748513a04 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala @@ -26,7 +26,11 @@ abstract class AbstractStringInterpreter( * @param instr The instruction that is to be interpreted. It is the responsibility of * implementations to make sure that an instruction is properly and comprehensively * evaluated. - * @return + * @return The interpreted instruction. An empty list that an instruction was not / could not be + * interpreted (e.g., because it is not supported or it was processed before). A list + * with more than one element indicates an option (only one out of the values is + * possible during runtime of the program); thus, all concats must already happen within + * the interpretation. */ def interpret(instr: T): List[StringConstancyInformation] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 1e13da2318..9e9aeed86d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -9,8 +9,6 @@ import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.tac.VirtualFunctionCall -import scala.collection.mutable.ListBuffer - /** * The `VirtualFunctionCallInterpreter` is responsible for processing [[VirtualFunctionCall]]s. * The list of currently supported function calls can be seen in the documentation of @@ -56,19 +54,24 @@ class VirtualFunctionCallInterpreter( private def interpretAppendCall( appendCall: VirtualFunctionCall[V] ): List[StringConstancyInformation] = { - val scis = ListBuffer[StringConstancyInformation]() - val receiverValue = receiverValueOfAppendCall(appendCall) - if (receiverValue.nonEmpty) { - scis.appendAll(receiverValue) - } - val appendValue = valueOfAppendCall(appendCall) - if (appendValue.isDefined) { - scis.append(appendValue.get) - } - scis.toList + if (receiverValue.isEmpty && appendValue.isEmpty) { + List() + } else if (receiverValue.isDefined && appendValue.isEmpty) { + List(receiverValue.get) + } else if (receiverValue.isEmpty && appendValue.nonEmpty) { + List(appendValue.get) + } else { + List(StringConstancyInformation( + StringConstancyLevel.determineForConcat( + receiverValue.get.constancyLevel, + appendValue.get.constancyLevel + ), + receiverValue.get.possibleStrings + appendValue.get.possibleStrings + )) + } } /** diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 38a07ff672..417e6a2235 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -73,9 +73,14 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { npe.element.size match { case 0 ⇒ None case 1 ⇒ pathToTreeAcc(npe.element.head) - case _ ⇒ Some(StringTreeConcat( - npe.element.map(pathToTreeAcc).filter(_.isDefined).map(_.get) - )) + case _ ⇒ + val processed = + npe.element.map(pathToTreeAcc).filter(_.isDefined).map(_.get) + if (processed.isEmpty) { + None + } else { + Some(StringTreeConcat(processed)) + } } } case _ ⇒ None From 24d71c583833f105631414dd902bd36793a09f33 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 16:55:59 +0100 Subject: [PATCH 042/316] `append` calls with an int or float value are now processed as well (in a very simple way though). --- .../BinaryExprInterpreter.scala | 10 +++--- .../VirtualFunctionCallInterpreter.scala | 31 +++++++++++++------ .../StringConstancyInformation.scala | 11 ++++++- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala index e8cd477a82..b8eceafb43 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala @@ -39,10 +39,12 @@ class BinaryExprInterpreter( */ override def interpret(instr: T): List[StringConstancyInformation] = instr.cTpe match { - case ComputationalTypeInt ⇒ - List(StringConstancyInformation(StringConstancyLevel.DYNAMIC, "[AnIntegerValue]")) - case ComputationalTypeFloat ⇒ - List(StringConstancyInformation(StringConstancyLevel.DYNAMIC, "[AFloatValue]")) + case ComputationalTypeInt ⇒ List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, StringConstancyInformation.IntValue + )) + case ComputationalTypeFloat ⇒ List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, StringConstancyInformation.FloatValue + )) case _ ⇒ List() } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 9e9aeed86d..43916943b1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -4,6 +4,8 @@ package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.br.cfg.CFG +import org.opalj.br.ComputationalTypeFloat +import org.opalj.br.ComputationalTypeInt import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel @@ -90,16 +92,27 @@ class VirtualFunctionCallInterpreter( call: VirtualFunctionCall[V] ): Option[StringConstancyInformation] = { val value = exprHandler.processDefSite(call.params.head.asVar.definedBy.head) - // It might be necessary to merge the value of the receiver and of the parameter together - value.size match { - case 0 ⇒ None - case 1 ⇒ value.headOption - case _ ⇒ Some(StringConstancyInformation( - StringConstancyLevel.determineForConcat( - value.head.constancyLevel, value(1).constancyLevel - ), - value.head.possibleStrings + value(1).possibleStrings + call.params.head.asVar.value.computationalType match { + // For some types, we know the (dynamic) values + case ComputationalTypeInt ⇒ Some(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, StringConstancyInformation.IntValue + )) + case ComputationalTypeFloat ⇒ Some(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, StringConstancyInformation.FloatValue )) + // Otherwise, try to compute + case _ ⇒ + // It might be necessary to merge the values of the receiver and of the parameter + value.size match { + case 0 ⇒ None + case 1 ⇒ value.headOption + case _ ⇒ Some(StringConstancyInformation( + StringConstancyLevel.determineForConcat( + value.head.constancyLevel, value(1).constancyLevel + ), + value.head.possibleStrings + value(1).possibleStrings + )) + } } } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 5f10efa2bc..0549a01c00 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -2,7 +2,6 @@ package org.opalj.fpcf.string_definition.properties /** - * * @author Patrick Mell */ case class StringConstancyInformation( @@ -17,6 +16,16 @@ object StringConstancyInformation { */ val UnknownWordSymbol: String = "\\w" + /** + * The stringified version of a (dynamic) integer value. + */ + val IntValue: String = "[AnIntegerValue]" + + /** + * The stringified version of a (dynamic) float value. + */ + val FloatValue: String = "[AFloatValue]" + /** * A value to be used when the number of an element, that is repeated, is unknown. */ From 3a1ae3d38c4579d21ca9e898369c208dc1183da4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 17:03:43 +0100 Subject: [PATCH 043/316] Updated the LocalStringDefinitionAnalysis to work with the new interpretation approach. --- .../LocalStringDefinitionAnalysis.scala | 74 +++++++++++-------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index f91e205999..10a80d4c04 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -6,21 +6,21 @@ import org.opalj.fpcf.FPCFAnalysis import org.opalj.fpcf.PropertyComputationResult import org.opalj.fpcf.PropertyKind import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.ComputationSpecification -import org.opalj.fpcf.analyses.string_definition.expr_processing.ExprHandler -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeCond +import org.opalj.fpcf.NoResult +import org.opalj.fpcf.analyses.string_definition.interpretation.NewStringBuilderProcessor +import org.opalj.fpcf.analyses.string_definition.preprocessing.AbstractPathFinder +import org.opalj.fpcf.analyses.string_definition.preprocessing.DefaultPathFinder +import org.opalj.fpcf.analyses.string_definition.preprocessing.PathTransformer +import org.opalj.fpcf.Result +import org.opalj.fpcf.analyses.string_definition.interpretation.ExprHandler import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt -import scala.collection.mutable.ArrayBuffer -import scala.collection.mutable.ListBuffer - class StringTrackingAnalysisContext( - val stmts: Array[Stmt[V]] + val stmts: Array[Stmt[V]] ) /** @@ -44,30 +44,46 @@ class LocalStringDefinitionAnalysis( def analyze(data: P): PropertyComputationResult = { val tacProvider = p.get(SimpleTACAIKey) val stmts = tacProvider(data._2).stmts + val cfg = tacProvider(data._2).cfg - val exprHandler = ExprHandler(p, data._2) val defSites = data._1.definedBy.toArray.sorted - if (ExprHandler.isStringBuilderToStringCall(stmts(defSites.head).asAssignment.expr)) { - val subtrees = ArrayBuffer[StringTree]() - defSites.foreach { nextDefSite ⇒ - val treeElements = ExprHandler.getDefSitesOfToStringReceiver( - stmts(nextDefSite).asAssignment.expr - ).map { exprHandler.processDefSite }.filter(_.isDefined).map { _.get } - if (treeElements.length == 1) { - subtrees.append(treeElements.head) - } else { - subtrees.append(StringTreeCond(treeElements.to[ListBuffer])) - } + val expr = stmts(defSites.head).asAssignment.expr + val pathFinder: AbstractPathFinder = new DefaultPathFinder() + if (ExprHandler.isStringBuilderToStringCall(expr)) { + val initDefSites = NewStringBuilderProcessor.findDefSiteOfInit( + expr.asVirtualFunctionCall, stmts + ) + if (initDefSites.isEmpty) { + throw new IllegalStateException("did not find any initializations!") } - val finalTree = if (subtrees.size == 1) subtrees.head else - StringTreeCond(subtrees.to[ListBuffer]) - Result(data, StringConstancyProperty(finalTree)) - } // If not a call to StringBuilder.toString, then we deal with pure strings + val paths = pathFinder.findPaths(initDefSites, data._1.definedBy.head, cfg) + val leanPaths = paths.makeLeanPath(data._1, stmts) + // The following case should only occur if an object is queried that does not occur at + // all within the CFG + if (leanPaths.isEmpty) { + return NoResult + } + + val tree = new PathTransformer(cfg).pathToStringTree(leanPaths.get) + if (tree.isDefined) { + Result(data, StringConstancyProperty(tree.get)) + } else { + NoResult + } + } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - Result(data, StringConstancyProperty( - exprHandler.processDefSites(data._1.definedBy.toArray).get - )) + val paths = pathFinder.findPaths(defSites.toList, data._1.definedBy.head, cfg) + if (paths.elements.isEmpty) { + NoResult + } else { + val tree = new PathTransformer(cfg).pathToStringTree(paths) + if (tree.isDefined) { + Result(data, StringConstancyProperty(tree.get)) + } else { + NoResult + } + } } } @@ -95,8 +111,8 @@ sealed trait LocalStringDefinitionAnalysisScheduler extends ComputationSpecifica * Executor for the lazy analysis. */ object LazyStringDefinitionAnalysis - extends LocalStringDefinitionAnalysisScheduler - with FPCFLazyAnalysisScheduler { + extends LocalStringDefinitionAnalysisScheduler + with FPCFLazyAnalysisScheduler { final override def startLazily( p: SomeProject, ps: PropertyStore, unused: Null From 9fa149dd008b1058bdee90af784075b94a2cf09a Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 17:04:40 +0100 Subject: [PATCH 044/316] Minor changes on the test cases. --- .../string_definition/TestMethods.java | 370 +++++++++--------- 1 file changed, 185 insertions(+), 185 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 6b370d5f9c..883efb5b34 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -106,22 +106,6 @@ public void directAppendConcats() { analyzeString(sb.toString()); } - // @StringDefinitions( - // value = "checks if a string value with > 2 continuous appends and a second " - // + "StringBuilder is determined correctly", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "java.langStringB." - // ) - // public void directAppendConcats2() { - // StringBuilder sb = new StringBuilder("java"); - // StringBuilder sb2 = new StringBuilder("B"); - // sb.append(".").append("lang"); - // sb2.append("."); - // sb.append("String"); - // sb.append(sb2.toString()); - // analyzeString(sb.toString()); - // } - @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", expectedLevel = StringConstancyLevel.DYNAMIC, @@ -179,7 +163,7 @@ public void multipleConstantDefSites(boolean cond) { value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(java.lang.Object|\\w|java.lang.System|java.lang.\\w)" + expectedStrings = "((java.lang.Object|\\w)|java.lang.System|java.lang.\\w|\\w)" ) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -205,47 +189,6 @@ public void multipleDefSites(int value) { analyzeString(s); } - // @StringDefinitions( - // value = "a case where multiple optional definition sites have to be considered.", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(b|c)?" - // ) - // public void multipleOptionalAppendSites(int value) { - // StringBuilder sb = new StringBuilder("a"); - // switch (value) { - // case 0: - // sb.append("b"); - // break; - // case 1: - // sb.append("c"); - // break; - // case 3: - // break; - // case 4: - // break; - // } - // analyzeString(sb.toString()); - // } - - // @StringDefinitions( - // value = "a case with a switch with missing breaks", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(bc|c)?" - // ) - // public void multipleOptionalAppendSites(int value) { - // StringBuilder sb = new StringBuilder("a"); - // switch (value) { - // case 0: - // sb.append("b"); - // case 1: - // sb.append("c"); - // break; - // case 2: - // break; - // } - // analyzeString(sb.toString()); - // } - @StringDefinitions( value = "if-else control structure which append to a string builder with an int expr", expectedLevel = StringConstancyLevel.DYNAMIC, @@ -409,139 +352,196 @@ public void ifWithoutElse() { analyzeString(sb.toString()); } - // @StringDefinitions( - // value = "an extensive example with many control structures", - // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" - // ) - // public void extensive(boolean cond) { - // StringBuilder sb = new StringBuilder(); - // if (cond) { - // sb.append("iv1"); - // } else { - // sb.append("iv2"); - // } - // System.out.println(sb); - // sb.append(": "); - // - // Random random = new Random(); - // while (random.nextFloat() > 5.) { - // if (random.nextInt() % 2 == 0) { - // sb.append("great!"); - // } - // } - // - // if (sb.indexOf("great!") > -1) { - // sb.append(getRuntimeClassName()); - // } - // - // analyzeString(sb.toString()); - // } - - // @StringDefinitions( - // value = "an extensive example with many control structures where appends follow " - // + "after the read", - // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // expectedStrings = "(iv1|iv2): " - // ) - // public void extensiveEarlyRead(boolean cond) { - // StringBuilder sb = new StringBuilder(); - // if (cond) { - // sb.append("iv1"); - // } else { - // sb.append("iv2"); - // } - // System.out.println(sb); - // sb.append(": "); + // // @StringDefinitions( + // // value = "a case where multiple optional definition sites have to be considered.", + // // expectedLevel = StringConstancyLevel.CONSTANT, + // // expectedStrings = "a(b|c)?" + // // ) + // // public void multipleOptionalAppendSites(int value) { + // // StringBuilder sb = new StringBuilder("a"); + // // switch (value) { + // // case 0: + // // sb.append("b"); + // // break; + // // case 1: + // // sb.append("c"); + // // break; + // // case 3: + // // break; + // // case 4: + // // break; + // // } + // // analyzeString(sb.toString()); + // // } // - // analyzeString(sb.toString()); + // // @StringDefinitions( + // // value = "a case with a switch with missing breaks", + // // expectedLevel = StringConstancyLevel.CONSTANT, + // // expectedStrings = "a(bc|c)?" + // // ) + // // public void multipleOptionalAppendSites(int value) { + // // StringBuilder sb = new StringBuilder("a"); + // // switch (value) { + // // case 0: + // // sb.append("b"); + // // case 1: + // // sb.append("c"); + // // break; + // // case 2: + // // break; + // // } + // // analyzeString(sb.toString()); + // // } + + // // @StringDefinitions( + // // value = "checks if a string value with > 2 continuous appends and a second " + // // + "StringBuilder is determined correctly", + // // expectedLevel = StringConstancyLevel.CONSTANT, + // // expectedStrings = "java.langStringB." + // // ) + // // public void directAppendConcats2() { + // // StringBuilder sb = new StringBuilder("java"); + // // StringBuilder sb2 = new StringBuilder("B"); + // // sb.append(".").append("lang"); + // // sb2.append("."); + // // sb.append("String"); + // // sb.append(sb2.toString()); + // // analyzeString(sb.toString()); + // // } + + // // @StringDefinitions( + // // value = "an extensive example with many control structures", + // // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + // // ) + // // public void extensive(boolean cond) { + // // StringBuilder sb = new StringBuilder(); + // // if (cond) { + // // sb.append("iv1"); + // // } else { + // // sb.append("iv2"); + // // } + // // System.out.println(sb); + // // sb.append(": "); + // // + // // Random random = new Random(); + // // while (random.nextFloat() > 5.) { + // // if (random.nextInt() % 2 == 0) { + // // sb.append("great!"); + // // } + // // } + // // + // // if (sb.indexOf("great!") > -1) { + // // sb.append(getRuntimeClassName()); + // // } + // // + // // analyzeString(sb.toString()); + // // } // - // Random random = new Random(); - // while (random.nextFloat() > 5.) { - // if (random.nextInt() % 2 == 0) { - // sb.append("great!"); - // } - // } + // // @StringDefinitions( + // // value = "an extensive example with many control structures where appends follow " + // // + "after the read", + // // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // // expectedStrings = "(iv1|iv2): " + // // ) + // // public void extensiveEarlyRead(boolean cond) { + // // StringBuilder sb = new StringBuilder(); + // // if (cond) { + // // sb.append("iv1"); + // // } else { + // // sb.append("iv2"); + // // } + // // System.out.println(sb); + // // sb.append(": "); + // // + // // analyzeString(sb.toString()); + // // + // // Random random = new Random(); + // // while (random.nextFloat() > 5.) { + // // if (random.nextInt() % 2 == 0) { + // // sb.append("great!"); + // // } + // // } + // // + // // if (sb.indexOf("great!") > -1) { + // // sb.append(getRuntimeClassName()); + // // } + // // } // - // if (sb.indexOf("great!") > -1) { - // sb.append(getRuntimeClassName()); - // } - // } - - // @StringDefinitions( - // value = "a case where a StringBuffer is used (instead of a StringBuilder)", - // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" - // ) - // public void extensiveStringBuffer(boolean cond) { - // StringBuffer sb = new StringBuffer(); - // if (cond) { - // sb.append("iv1"); - // } else { - // sb.append("iv2"); - // } - // System.out.println(sb); - // sb.append(": "); + // // @StringDefinitions( + // // value = "a case where a StringBuffer is used (instead of a StringBuilder)", + // // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + // // ) + // // public void extensiveStringBuffer(boolean cond) { + // // StringBuffer sb = new StringBuffer(); + // // if (cond) { + // // sb.append("iv1"); + // // } else { + // // sb.append("iv2"); + // // } + // // System.out.println(sb); + // // sb.append(": "); + // // + // // Random random = new Random(); + // // while (random.nextFloat() > 5.) { + // // if (random.nextInt() % 2 == 0) { + // // sb.append("great!"); + // // } + // // } + // // + // // if (sb.indexOf("great!") > -1) { + // // sb.append(getRuntimeClassName()); + // // } + // // + // // analyzeString(sb.toString()); + // // } // - // Random random = new Random(); - // while (random.nextFloat() > 5.) { - // if (random.nextInt() % 2 == 0) { - // sb.append("great!"); - // } - // } + // // @StringDefinitions( + // // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " + // // + "Michael Eichberg)?", + // // expectedLevel = StringConstancyLevel.CONSTANT, + // // expectedStrings = "a(b)*" + // // ) + // // public void whileTrue() { + // // StringBuilder sb = new StringBuilder("a"); + // // while (true) { + // // sb.append("b"); + // // } + // // analyzeString(sb.toString()); + // // } // - // if (sb.indexOf("great!") > -1) { - // sb.append(getRuntimeClassName()); - // } + // // @StringDefinitions( + // // value = "case with a nested loop where in the outer loop a StringBuilder is created " + // // + "that is later read (TODO: As Michael Eichberg meant?)", + // // expectedLevel = StringConstancyLevel.CONSTANT, + // // expectedStrings = "a(b)*" + // // ) + // // public void nestedLoops(int range) { + // // for (int i = 0; i < range; i++) { + // // StringBuilder sb = new StringBuilder("a"); + // // for (int j = 0; j < range * range; j++) { + // // sb.append("b"); + // // } + // // analyzeString(sb.toString()); + // // } + // // } // - // analyzeString(sb.toString()); - // } - - // @StringDefinitions( - // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " - // + "Michael Eichberg)?", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(b)*" - // ) - // public void whileTrue() { - // StringBuilder sb = new StringBuilder("a"); - // while (true) { - // sb.append("b"); - // } - // analyzeString(sb.toString()); - // } - - // @StringDefinitions( - // value = "case with a nested loop where in the outer loop a StringBuilder is created " - // + "that is later read (TODO: As Michael Eichberg meant?)", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(b)*" - // ) - // public void nestedLoops(int range) { - // for (int i = 0; i < range; i++) { - // StringBuilder sb = new StringBuilder("a"); - // for (int j = 0; j < range * range; j++) { - // sb.append("b"); - // } - // analyzeString(sb.toString()); - // } - // } - - // @StringDefinitions( - // value = "case with an exception", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "(File Content: |File Content: *)" - // ) - // public void withException(String filename) { - // StringBuilder sb = new StringBuilder("File Content: "); - // try { - // String data = new String(Files.readAllBytes(Paths.get(filename))); - // sb.append(data); - // } catch (Exception ignore) { - // } finally { - // analyzeString(sb.toString()); - // } - // } + // // @StringDefinitions( + // // value = "case with an exception", + // // expectedLevel = StringConstancyLevel.CONSTANT, + // // expectedStrings = "(File Content: |File Content: *)" + // // ) + // // public void withException(String filename) { + // // StringBuilder sb = new StringBuilder("File Content: "); + // // try { + // // String data = new String(Files.readAllBytes(Paths.get(filename))); + // // sb.append(data); + // // } catch (Exception ignore) { + // // } finally { + // // analyzeString(sb.toString()); + // // } + // // } private String getRuntimeClassName() { return "java.lang.Runtime"; From 36db21bdc5faa461be76d253c9e6c65309cd990e Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 17:26:19 +0100 Subject: [PATCH 045/316] Cleaned the code and refactored a bit. --- .../LocalStringDefinitionAnalysis.scala | 7 +- .../AbstractExprProcessor.scala | 67 ------- .../AbstractStringInterpreter.scala | 4 +- .../interpretation/ArrayLoadInterpreter.scala | 2 +- .../interpretation/ArrayLoadProcessor.scala | 81 -------- .../BinaryExprInterpreter.scala | 2 +- ...dler.scala => InterpretationHandler.scala} | 124 +++++------- .../interpretation/NewInterpreter.scala | 2 +- .../NewStringBuilderProcessor.scala | 90 --------- .../NonVirtualFunctionCallInterpreter.scala | 2 +- .../NonVirtualFunctionCallProcessor.scala | 66 ------- .../NonVirtualMethodCallInterpreter.scala | 2 +- .../StringConstInterpreter.scala | 2 +- .../interpretation/StringConstProcessor.scala | 63 ------- .../VirtualFunctionCallInterpreter.scala | 2 +- .../VirtualFunctionCallProcessor.scala | 178 ------------------ .../preprocessing/PathTransformer.scala | 10 +- 17 files changed, 69 insertions(+), 635 deletions(-) delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/{ExprHandler.scala => InterpretationHandler.scala} (58%) delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala delete mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 10a80d4c04..a06691af5e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -10,12 +10,11 @@ import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.NoResult -import org.opalj.fpcf.analyses.string_definition.interpretation.NewStringBuilderProcessor import org.opalj.fpcf.analyses.string_definition.preprocessing.AbstractPathFinder import org.opalj.fpcf.analyses.string_definition.preprocessing.DefaultPathFinder import org.opalj.fpcf.analyses.string_definition.preprocessing.PathTransformer import org.opalj.fpcf.Result -import org.opalj.fpcf.analyses.string_definition.interpretation.ExprHandler +import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt @@ -49,8 +48,8 @@ class LocalStringDefinitionAnalysis( val defSites = data._1.definedBy.toArray.sorted val expr = stmts(defSites.head).asAssignment.expr val pathFinder: AbstractPathFinder = new DefaultPathFinder() - if (ExprHandler.isStringBuilderToStringCall(expr)) { - val initDefSites = NewStringBuilderProcessor.findDefSiteOfInit( + if (InterpretationHandler.isStringBuilderToStringCall(expr)) { + val initDefSites = InterpretationHandler.findDefSiteOfInit( expr.asVirtualFunctionCall, stmts ) if (initDefSites.isEmpty) { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala deleted file mode 100644 index 879b06109e..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractExprProcessor.scala +++ /dev/null @@ -1,67 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation - -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts - -/** - * AbstractExprProcessor defines the abstract / general strategy to process expressions in the - * context of string definition analyses. Different sub-classes process different kinds of - * expressions. The idea is to transform expressions into [[StringTree]] objects. For example, the - * expression of a constant assignment might be processed. - * - * @author Patrick Mell - */ -abstract class AbstractExprProcessor() { - - /** - * Implementations process an assignment which is supposed to yield a string tree. - * - * @param assignment The Assignment to process. Make sure that the assignment, which is - * passed, meets the requirements of that implementation. - * @param stmts The statements that surround the expression to process, such as a method. - * Concrete processors might use these to retrieve further information. - * @param cfg The control flow graph that corresponds to the given `stmts` - * @param ignore A list of processed def or use sites. This list makes sure that an assignment - * or expression is not processed twice (which could lead to duplicate - * computations and unnecessary elements in the resulting string tree. - * @return Determines the [[StringTree]] for the given `expr` and `stmts` from which possible - * string values, which the expression might produce, can be derived. If `expr` does not - * meet the requirements of a an implementation, `None` will be returned (or in severe - * cases an exception be thrown). - * @see StringConstancyProperty - */ - def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] - - /** - * Implementations process an expression which is supposed to yield a string tree. - * - * @param expr The [[Expr]] to process. Make sure that the expression, which is passed, meets - * the requirements of the corresponding implementation. - * @param stmts The statements that surround the expression to process, such as a method. - * Concrete processors might use these to retrieve further information. - * @param ignore A list of processed def or use sites. This list makes sure that an assignment - * or expression is not processed twice (which could lead to duplicate - * computations and unnecessary elements in the resulting string tree. - * @return Determines the [[StringTree]] for the given `expr` from which possible string values, - * which the expression might produce, can be derived. If `expr` does not - * meet the requirements of a an implementation, `None` will be returned (or in severe - * cases an exception be thrown). - * - * @note Note that implementations of [[AbstractExprProcessor]] are not required to implement - * this method (by default, `None` will be returned. - */ - def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = { None } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala index 9748513a04..9112f02358 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala @@ -15,8 +15,8 @@ import org.opalj.tac.TACStmts * @author Patrick Mell */ abstract class AbstractStringInterpreter( - protected val cfg: CFG[Stmt[V], TACStmts[V]], - protected val exprHandler: ExprHandler, + protected val cfg: CFG[Stmt[V], TACStmts[V]], + protected val exprHandler: InterpretationHandler, ) { type T <: Any diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala index c0c0aa9d10..59dc5ee50e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala @@ -19,7 +19,7 @@ import scala.collection.mutable.ListBuffer */ class ArrayLoadInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = ArrayLoad[V] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala deleted file mode 100644 index 00bc9f4360..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadProcessor.scala +++ /dev/null @@ -1,81 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts - -/** - * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.ArrayLoad]] - * expressions. - * - * @param exprHandler As this expression processor will encounter other expressions outside its - * scope, such as StringConst or NonVirtualFunctionCall, an [[ExprHandler]] is - * required. - * - * @author Patrick Mell - */ -class ArrayLoadProcessor( - private val exprHandler: ExprHandler -) extends AbstractExprProcessor { - - /** - * The `expr` of `assignment`is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise - * `None` will be returned). - * - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(assignment.expr, stmts, ignore) - - /** - * The `expr` of `assignment`is required to be of type [[org.opalj.tac.ArrayLoad]] (otherwise - * * `None` will be returned). - * * - * * @see [[AbstractExprProcessor.processExpr]] - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(expr, stmts, ignore) - - /** - * Wrapper function for processing an expression. - */ - private def process( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = { - None - // expr match { - // case al: ArrayLoad[V] ⇒ - // val children = ListBuffer[StringTreeElement]() - // // Loop over all possible array values - // al.arrayRef.asVar.definedBy.toArray.sorted.foreach { next ⇒ - // val arrDecl = stmts(next) - // val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted - // sortedArrDeclUses.filter { - // stmts(_).isInstanceOf[ArrayStore[V]] - // } foreach { f: Int ⇒ - // val sortedSDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - // val arrValues = sortedSDefs.map { - // exprHandler.processDefSite - // }.filter(_.isDefined).map(_.get) - // children.appendAll(arrValues) - // } - // } - // - // if (children.nonEmpty) { - // Some(StringTreeOr(children)) - // } else { - // None - // } - // case _ ⇒ None - // } - } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala index b8eceafb43..27353b564b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala @@ -20,7 +20,7 @@ import org.opalj.tac.BinaryExpr */ class BinaryExprInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = BinaryExpr[V] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala similarity index 58% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index e7467e6997..dce66ff076 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ExprHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -17,19 +17,19 @@ import org.opalj.tac.StringConst import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall +import scala.collection.mutable import scala.collection.mutable.ListBuffer /** - * `ExprHandler` is responsible for processing expressions that are relevant in order to determine - * which value(s) a string read operation might have. These expressions usually come from the - * definitions sites of the variable of interest. + * `InterpretationHandler` is responsible for processing expressions that are relevant in order to + * determine which value(s) a string read operation might have. These expressions usually come from + * the definitions sites of the variable of interest. * * @param cfg The control flow graph that underlies the program / method in which the expressions of * interest reside. * @author Patrick Mell */ -class ExprHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { - +class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { private val stmts = cfg.code.instructions private val processedDefSites = ListBuffer[Int]() @@ -52,38 +52,38 @@ class ExprHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { processedDefSites.append(defSite) stmts(defSite) match { - case Assignment(_, _, expr) if expr.isInstanceOf[StringConst] => + case Assignment(_, _, expr) if expr.isInstanceOf[StringConst] ⇒ new StringConstInterpreter(cfg, this).interpret(expr.asStringConst) - case Assignment(_, _, expr) if expr.isInstanceOf[ArrayLoad[V]] => + case Assignment(_, _, expr) if expr.isInstanceOf[ArrayLoad[V]] ⇒ new ArrayLoadInterpreter(cfg, this).interpret(expr.asArrayLoad) - case Assignment(_, _, expr) if expr.isInstanceOf[New] => + case Assignment(_, _, expr) if expr.isInstanceOf[New] ⇒ new NewInterpreter(cfg, this).interpret(expr.asNew) - case Assignment(_, _, expr) if expr.isInstanceOf[VirtualFunctionCall[V]] => + case Assignment(_, _, expr) if expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ new VirtualFunctionCallInterpreter(cfg, this).interpret(expr.asVirtualFunctionCall) - case Assignment(_, _, expr) if expr.isInstanceOf[BinaryExpr[V]] => + case Assignment(_, _, expr) if expr.isInstanceOf[BinaryExpr[V]] ⇒ new BinaryExprInterpreter(cfg, this).interpret(expr.asBinaryExpr) - case Assignment(_, _, expr) if expr.isInstanceOf[NonVirtualFunctionCall[V]] => + case Assignment(_, _, expr) if expr.isInstanceOf[NonVirtualFunctionCall[V]] ⇒ new NonVirtualFunctionCallInterpreter( cfg, this ).interpret(expr.asNonVirtualFunctionCall) - case ExprStmt(_, expr) => + case ExprStmt(_, expr) ⇒ expr match { - case vfc: VirtualFunctionCall[V] => + case vfc: VirtualFunctionCall[V] ⇒ new VirtualFunctionCallInterpreter(cfg, this).interpret(vfc) - case _ => List() + case _ ⇒ List() } - case nvmc: NonVirtualMethodCall[V] => + case nvmc: NonVirtualMethodCall[V] ⇒ new NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) - case _ => List() + case _ ⇒ List() } } /** - * This function serves as a wrapper function for [[ExprHandler.processDefSite]] in the + * This function serves as a wrapper function for [[InterpretationHandler.processDefSite]] in the * sense that it processes multiple definition sites. Thus, it may throw an exception as well if * an expression referenced by a definition site cannot be processed. The same rules as for - * [[ExprHandler.processDefSite]] apply. + * [[InterpretationHandler.processDefSite]] apply. * * @param defSites The definition sites to process. * @return Returns a list of lists of [[StringConstancyInformation]]. Note that this function @@ -99,11 +99,11 @@ class ExprHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { } /** - * The [[ExprHandler]] keeps an internal state for correct and faster processing. As long as a + * The [[InterpretationHandler]] keeps an internal state for correct and faster processing. As long as a * single object within a CFG is analyzed, there is no need to reset the state. However, when * analyzing a second object (even the same object) it is necessary to call `reset` to reset the * internal state. Otherwise, incorrect results will be produced. - * (Alternatively, you could instantiate another [[ExprHandler]] instance.) + * (Alternatively, you could instantiate another [[InterpretationHandler]] instance.) */ def reset(): Unit = { processedDefSites.clear() @@ -111,30 +111,12 @@ class ExprHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { } -object ExprHandler { - - private val classNameMap = Map( - "AnIntegerValue" → "[AnIntegerValue]", - "int" → "[AnIntegerValue]", - "IntegerRange" → "[AnIntegerValue]", - ) - - /** - * @see [[ExprHandler]] - */ - def apply(cfg: CFG[Stmt[V], TACStmts[V]]): ExprHandler = new ExprHandler(cfg) +object InterpretationHandler { /** - * Checks whether the given definition site is within a loop. - * - * @param defSite The definition site to check. - * @param cfg The control flow graph which is required for that operation. - * @return Returns `true` if the given site resides within a loop and `false` otherwise. + * @see [[InterpretationHandler]] */ - def isWithinLoop(defSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = - cfg.findNaturalLoops().foldLeft(false) { (previous: Boolean, nextLoop: List[Int]) ⇒ - previous || nextLoop.contains(defSite) - } + def apply(cfg: CFG[Stmt[V], TACStmts[V]]): InterpretationHandler = new InterpretationHandler(cfg) /** * Checks whether an expression contains a call to [[StringBuilder.toString]]. @@ -150,42 +132,40 @@ object ExprHandler { } /** - * Checks whether an expression is a call to [[StringBuilder#append]]. + * Determines the definition site of the initialization of the base object that belongs to a + * ''toString'' call. * - * @param expr The expression that is to be checked. - * @return Returns true if `expr` is a call to [[StringBuilder#append]]. + * @param toString The ''toString'' call of the object for which to get the initialization def + * site for. Make sure that the object is a subclass of + * [[AbstractStringBuilder]]. + * @param stmts A list of statements which will be used to lookup which one the initialization + * is. + * @return Returns the definition site of the base object of the call. If something goes wrong, + * e.g., no initialization is found, ''None'' is returned. */ - def isStringBuilderAppendCall(expr: Expr[V]): Boolean = - expr match { - case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ - clazz.toJavaClass.getName == "java.lang.StringBuilder" && name == "append" - case _ ⇒ false + def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { + // TODO: Check that we deal with an instance of AbstractStringBuilder + if (toString.name != "toString") { + return List() } - /** - * Retrieves the definition sites of the receiver of a [[StringBuilder.toString]] call. - * - * @param expr The expression that contains the receiver whose definition sites to get. - * @return If `expr` does not conform to the expected structure, an empty array is - * returned (avoid by using [[isStringBuilderToStringCall]]) and otherwise the - * definition sites of the receiver. - */ - def getDefSitesOfToStringReceiver(expr: Expr[V]): Array[Int] = - if (!isStringBuilderToStringCall(expr)) { - Array() - } else { - expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray.sorted + val defSites = ListBuffer[Int]() + val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.toArray: _*) + while (stack.nonEmpty) { + val next = stack.pop() + stmts(next) match { + case a: Assignment[V] ⇒ + a.expr match { + case _: New ⇒ + defSites.append(next) + case vfc: VirtualFunctionCall[V] ⇒ + stack.pushAll(vfc.receiver.asVar.definedBy.toArray) + } + case _ ⇒ + } } - /** - * Maps a class name to a string which is to be displayed as a possible string. - * - * @param javaSimpleClassName The simple class name, i.e., NOT fully-qualified, for which to - * retrieve the value for "possible string". - * @return Either returns the mapped string representation or, when an unknown string is passed, - * the passed parameter surrounded by "[" and "]". - */ - def classNameToPossibleString(javaSimpleClassName: String): String = - classNameMap.getOrElse(javaSimpleClassName, s"[$javaSimpleClassName]") + defSites.sorted.toList + } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala index ede64b2673..9c43cc15c5 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala @@ -17,7 +17,7 @@ import org.opalj.tac.New */ class NewInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = New diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala deleted file mode 100644 index 7674277723..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewStringBuilderProcessor.scala +++ /dev/null @@ -1,90 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation - -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.tac.Stmt -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.New -import org.opalj.tac.TACStmts -import org.opalj.tac.VirtualFunctionCall - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -/** - * - * @author Patrick Mell - */ -class NewStringBuilderProcessor( - private val exprHandler: ExprHandler -) extends AbstractExprProcessor { - - /** - * `expr` of `assignment`is required to be of type [[org.opalj.tac.New]] (otherwise `None` will - * be returned). - * - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = { - assignment.expr match { - case _ ⇒ None - } - } - - /** - * This implementation does not change / implement the behavior of - * [[AbstractExprProcessor.processExpr]]. - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = super.processExpr(expr, stmts, cfg, ignore) - -} - -object NewStringBuilderProcessor { - - /** - * Determines the definition site of the initialization of the base object that belongs to a - * ''toString'' call. - * - * @param toString The ''toString'' call of the object for which to get the initialization def - * site for. Make sure that the object is a subclass of - * [[AbstractStringBuilder]]. - * @param stmts A list of statements which will be used to lookup which one the initialization - * is. - * @return Returns the definition site of the base object of the call. If something goes wrong, - * e.g., no initialization is found, ''None'' is returned. - */ - def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { - // TODO: Check that we deal with an instance of AbstractStringBuilder - if (toString.name != "toString") { - return List() - } - - val defSites = ListBuffer[Int]() - val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.toArray: _*) - while (stack.nonEmpty) { - val next = stack.pop() - stmts(next) match { - case a: Assignment[V] ⇒ - a.expr match { - case _: New ⇒ - defSites.append(next) - case vfc: VirtualFunctionCall[V] ⇒ - stack.pushAll(vfc.receiver.asVar.definedBy.toArray) - } - case _ ⇒ - } - } - - defSites.sorted.toList - } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala index 1d3e97283a..0177ab1043 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala @@ -19,7 +19,7 @@ import org.opalj.tac.TACStmts */ class NonVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala deleted file mode 100644 index ac9b667fc2..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallProcessor.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation - -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.UnknownWordSymbol -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConst -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.NonVirtualFunctionCall -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts - -/** - * This implementation of [[AbstractExprProcessor]] processes - * [[org.opalj.tac.NonVirtualFunctionCall]] expressions. - * Currently, this implementation is only a rough approximation in the sense that all - * `NonVirtualFunctionCall`s are processed by returning a [[StringTreeConst]] with no children - * and `StringConstancyProperty(DYNAMIC, ArrayBuffer("*"))` as a value (i.e., it does not analyze - * the function call in depth). - * - * @author Patrick Mell - */ -class NonVirtualFunctionCallProcessor() extends AbstractExprProcessor { - - /** - * `expr` of `assignment`is required to be of type [[org.opalj.tac.NonVirtualFunctionCall]] - * (otherwise `None` will be returned). - * `stmts` currently is not relevant, thus an empty array may be passed. - * - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(assignment.expr, stmts, ignore) - - /** - * `expr` is required to be of type [[org.opalj.tac.NonVirtualFunctionCall]] (otherwise `None` - * will be returned). `stmts` currently is not relevant, thus an empty array may be passed. - * - * @see [[AbstractExprProcessor.processExpr()]] - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(expr, stmts, ignore) - - /** - * Wrapper function for processing. - */ - private def process( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = { - expr match { - case _: NonVirtualFunctionCall[V] ⇒ Some(StringTreeConst( - StringConstancyInformation(DYNAMIC, UnknownWordSymbol) - )) - case _ ⇒ None - } - } - -} \ No newline at end of file diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala index 2f822ba5c7..0e04ccecba 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala @@ -20,7 +20,7 @@ import scala.collection.mutable.ListBuffer */ class NonVirtualMethodCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala index bbf354b9ae..2c45801515 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala @@ -18,7 +18,7 @@ import org.opalj.tac.StringConst */ class StringConstInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StringConst diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala deleted file mode 100644 index 637cb2ac09..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstProcessor.scala +++ /dev/null @@ -1,63 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConst -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.Stmt -import org.opalj.tac.StringConst -import org.opalj.tac.TACStmts - -/** - * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.StringConst]] - * expressions. - * - * @author Patrick Mell - */ -class StringConstProcessor() extends AbstractExprProcessor { - - /** - * For this implementation, `stmts` is not required (thus, it is safe to pass an empty value). - * The `expr` of `assignment` is required to be of type [[org.opalj.tac.StringConst]] (otherwise - * `None` will be returned). - * - * @note The sub-tree, which is created by this implementation, does not have any children. - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(assignment.expr, stmts, ignore) - - /** - * For this implementation, `stmts` is not required (thus, it is safe to pass an empty value). - * `expr` is required to be of type [[org.opalj.tac.StringConst]] (otherwise `None` will be - * returned). - * - * @note The sub-tree, which is created by this implementation, does not have any children. - * @see [[AbstractExprProcessor.processExpr()]] - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(expr, stmts, ignore) - - /** - * Wrapper function for processing an expression. - */ - private def process( - expr: Expr[V], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = { - expr match { - case strConst: StringConst ⇒ Some(StringTreeConst( - StringConstancyInformation(CONSTANT, strConst.value) - )) - case _ ⇒ None - } - } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 43916943b1..6bfe2ccc5f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -22,7 +22,7 @@ import org.opalj.tac.VirtualFunctionCall */ class VirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: ExprHandler + exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala deleted file mode 100644 index 6399e58260..0000000000 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallProcessor.scala +++ /dev/null @@ -1,178 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation - -import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.tac.Assignment -import org.opalj.tac.Expr -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts - -/** - * This implementation of [[AbstractExprProcessor]] processes [[org.opalj.tac.VirtualFunctionCall]] - * expressions. - * Currently, [[VirtualFunctionCallProcessor]] (only) aims at processing calls of - * [[StringBuilder#append]]. - * - * @author Patrick Mell - */ -class VirtualFunctionCallProcessor( - private val exprHandler: ExprHandler, - private val cfg: CFG[Stmt[V], TACStmts[V]] -) extends AbstractExprProcessor { - - /** - * `expr` of `assignment`is required to be of type [[org.opalj.tac.VirtualFunctionCall]] - * (otherwise `None` will be returned). - * - * @see [[AbstractExprProcessor.processAssignment]] - */ - override def processAssignment( - assignment: Assignment[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(assignment.expr, Some(assignment), stmts, ignore) - - /** - * @see [[AbstractExprProcessor.processExpr]]. - * - * @note For expressions, some information are not available that an [[Assignment]] captures. - * Nonetheless, as much information as possible is extracted from this implementation (but - * no use sites for `append` calls, for example). - */ - override def processExpr( - expr: Expr[V], stmts: Array[Stmt[V]], cfg: CFG[Stmt[V], TACStmts[V]], - ignore: List[Int] = List[Int]() - ): Option[StringTree] = process(expr, None, stmts, ignore) - - /** - * Wrapper function for processing assignments. - */ - private def process( - expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] - ): Option[StringTree] = { - None - // expr match { - // case vfc: VirtualFunctionCall[V] ⇒ - // if (ExprHandler.isStringBuilderAppendCall(expr)) { - // processAppendCall(expr, assignment, stmts, ignore) - // } else if (ExprHandler.isStringBuilderToStringCall(expr)) { - // processToStringCall(assignment, stmts, ignore) - // } // A call to method which is not (yet) supported - // else { - // val ps = ExprHandler.classNameToPossibleString( - // vfc.descriptor.returnType.toJavaClass.getSimpleName - // ) - // Some(StringTreeConst(StringConstancyInformation(DYNAMIC, ps))) - // } - // case _ ⇒ None - // } - } - - /** - * Function for processing calls to [[StringBuilder#append]]. - */ - // private def processAppendCall( - // expr: Expr[V], assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] - // ): Option[StringTreeElement] = { - // val defSites = expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray.sorted - // val appendValue = valueOfAppendCall(expr.asVirtualFunctionCall, stmts, ignore) - // // Append has been processed before => do not compute again - // if (appendValue.isEmpty) { - // return None - // } - // - // val leftSiblings = exprHandler.processDefSites(defSites) - // // For assignments, we can take use sites into consideration as well - // var rightSiblings: Option[StringTree] = None - // if (assignment.isDefined) { - // val useSites = assignment.get.targetVar.asVar.usedBy.toArray.sorted - // rightSiblings = exprHandler.processDefSites(useSites) - // } - // - // if (leftSiblings.isDefined || rightSiblings.isDefined) { - // // Combine siblings and return - // val concatElements = ListBuffer[StringTreeElement]() - // if (leftSiblings.isDefined) { - // concatElements.append(leftSiblings.get) - // } - // concatElements.append(appendValue.get) - // if (rightSiblings.isDefined) { - // concatElements.append(rightSiblings.get) - // } - // Some(StringTreeConcat(concatElements)) - // } else { - // Some(appendValue.get) - // } - // } - - /** - * Function for processing calls to [[StringBuilder.toString]]. Note that a value not equals to - * `None` can only be expected if `assignments` is defined. - */ - // private def processToStringCall( - // assignment: Option[Assignment[V]], stmts: Array[Stmt[V]], ignore: List[Int] - // ): Option[StringTree] = { - // if (assignment.isEmpty) { - // return None - // } - // - // val children = ListBuffer[StringTreeElement]() - // val call = assignment.get.expr.asVirtualFunctionCall - // val defSites = call.receiver.asVar.definedBy.filter(!ignore.contains(_)) - // defSites.foreach { - // exprHandler.processDefSite(_) match { - // case Some(subtree) ⇒ children.append(subtree) - // case None ⇒ - // } - // } - // - // children.size match { - // case 0 ⇒ None - // case 1 ⇒ Some(children.head) - // case _ ⇒ Some(StringTreeCond(children)) - // } - // } - - /** - * Determines the string value that was passed to a `StringBuilder#append` method. This function - * can process string constants as well as function calls as argument to append. - * - * @param call A function call of `StringBuilder#append`. Note that for all other methods an - * [[IllegalArgumentException]] will be thrown. - * @param stmts The surrounding context, e.g., the surrounding method. - * @return Returns a [[org.opalj.fpcf.string_definition.properties.StringTreeConst]] with no children and the following value for - * [[org.opalj.fpcf.string_definition.properties.StringConstancyInformation]]: For constants strings as arguments, this function - * returns the string value and the level - * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT]]. For - * function calls "*" (to indicate ''any value'') and - * [[org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC]]. - */ - // private def valueOfAppendCall( - // call: VirtualFunctionCall[V], stmts: Array[Stmt[V]], ignore: List[Int] - // ): Option[StringTreeConst] = { - // val defAssignment = call.params.head.asVar.definedBy.head - // // The definition has been seen before => do not recompute - // if (ignore.contains(defAssignment)) { - // return None - // } - // - // val assign = stmts(defAssignment).asAssignment - // val sci = assign.expr match { - // case _: NonVirtualFunctionCall[V] ⇒ - // StringConstancyInformation(DYNAMIC, UnknownWordSymbol) - // case StringConst(_, value) ⇒ - // StringConstancyInformation(CONSTANT, value) - // // Next case is for an append call as argument to append - // case _: VirtualFunctionCall[V] ⇒ - // processAssignment(assign, stmts, cfg).get.reduce() - // case be: BinaryExpr[V] ⇒ - // val possibleString = ExprHandler.classNameToPossibleString( - // be.left.asVar.value.getClass.getSimpleName - // ) - // StringConstancyInformation(DYNAMIC, possibleString) - // } - // Some(StringTreeConst(sci)) - // } - -} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 417e6a2235..0eb52573c2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -3,7 +3,7 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.analyses.string_definition.interpretation.ExprHandler +import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.StringTreeConcat import org.opalj.fpcf.string_definition.properties.StringTreeCond @@ -28,7 +28,7 @@ import scala.collection.mutable.ListBuffer */ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { - private val exprHandler = ExprHandler(cfg) + private val exprHandler = InterpretationHandler(cfg) /** * Accumulator function for transforming a path into a StringTree element. @@ -91,10 +91,10 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * Takes a [[Path]] and transforms it into a [[StringTree]]. This implies an interpretation of * how to handle methods called on the object of interest (like `append`). * - * @param path The path element to be transformed. - * @param resetExprHandler Whether to reset the underlying [[ExprHandler]] or not. When calling + * @param path The path element to be transformed. + * @param resetExprHandler Whether to reset the underlying [[InterpretationHandler]] or not. When calling * this function from outside, the default value should do fine in most - * of the cases. For further information, see [[ExprHandler.reset]]. + * of the cases. For further information, see [[InterpretationHandler.reset]]. * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed * [[StringTree]] will be returned. Note that all elements of the tree will be defined, * i.e., if `path` contains sites that could not be processed (successfully), they will From 05e3987b89d40344c9ce010d6321c7fd900cc99c Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 21:25:46 +0100 Subject: [PATCH 046/316] Enabled a further working test case. --- .../string_definition/TestMethods.java | 135 +++++++++--------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 883efb5b34..1c50ce7868 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -352,46 +352,75 @@ public void ifWithoutElse() { analyzeString(sb.toString()); } - // // @StringDefinitions( - // // value = "a case where multiple optional definition sites have to be considered.", - // // expectedLevel = StringConstancyLevel.CONSTANT, - // // expectedStrings = "a(b|c)?" - // // ) - // // public void multipleOptionalAppendSites(int value) { - // // StringBuilder sb = new StringBuilder("a"); - // // switch (value) { - // // case 0: - // // sb.append("b"); - // // break; - // // case 1: - // // sb.append("c"); - // // break; - // // case 3: - // // break; - // // case 4: - // // break; - // // } - // // analyzeString(sb.toString()); - // // } + @StringDefinitions( + value = "a case where multiple optional definition sites have to be considered.", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b|c)?" + ) + public void multipleOptionalAppendSites(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 3: + break; + case 4: + break; + } + analyzeString(sb.toString()); + } + + // @StringDefinitions( + // value = "an extensive example with many control structures", + // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + // ) + // public void extensive(boolean cond) { + // StringBuilder sb = new StringBuilder(); + // if (cond) { + // sb.append("iv1"); + // } else { + // sb.append("iv2"); + // } + // System.out.println(sb); + // sb.append(": "); // - // // @StringDefinitions( - // // value = "a case with a switch with missing breaks", - // // expectedLevel = StringConstancyLevel.CONSTANT, - // // expectedStrings = "a(bc|c)?" - // // ) - // // public void multipleOptionalAppendSites(int value) { - // // StringBuilder sb = new StringBuilder("a"); - // // switch (value) { - // // case 0: - // // sb.append("b"); - // // case 1: - // // sb.append("c"); - // // break; - // // case 2: - // // break; - // // } - // // analyzeString(sb.toString()); - // // } + // Random random = new Random(); + // while (random.nextFloat() > 5.) { + // if (random.nextInt() % 2 == 0) { + // sb.append("great!"); + // } + // } + // + // if (sb.indexOf("great!") > -1) { + // sb.append(getRuntimeClassName()); + // } + // + // analyzeString(sb.toString()); + // } + +// @StringDefinitions( +// value = "a case with a switch with missing breaks", +// expectedLevel = StringConstancyLevel.CONSTANT, +// expectedStrings = "a(bc|c)?" +// ) +// public void switchWithMissingBreak(int value) { +// StringBuilder sb = new StringBuilder("a"); +// switch (value) { +// case 0: +// sb.append("b"); +// case 1: +// sb.append("c"); +// break; +// case 2: +// break; +// } +// analyzeString(sb.toString()); +// } // // @StringDefinitions( // // value = "checks if a string value with > 2 continuous appends and a second " @@ -409,34 +438,6 @@ public void ifWithoutElse() { // // analyzeString(sb.toString()); // // } - // // @StringDefinitions( - // // value = "an extensive example with many control structures", - // // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" - // // ) - // // public void extensive(boolean cond) { - // // StringBuilder sb = new StringBuilder(); - // // if (cond) { - // // sb.append("iv1"); - // // } else { - // // sb.append("iv2"); - // // } - // // System.out.println(sb); - // // sb.append(": "); - // // - // // Random random = new Random(); - // // while (random.nextFloat() > 5.) { - // // if (random.nextInt() % 2 == 0) { - // // sb.append("great!"); - // // } - // // } - // // - // // if (sb.indexOf("great!") > -1) { - // // sb.append(getRuntimeClassName()); - // // } - // // - // // analyzeString(sb.toString()); - // // } // // // @StringDefinitions( // // value = "an extensive example with many control structures where appends follow " From ab966ba0a7b88ddcc9e902325dd577f543ed4910 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 21:40:42 +0100 Subject: [PATCH 047/316] 1) Early stop analyzing when the desired code instruction was reached 2) Enabled further working test cases --- .../string_definition/TestMethods.java | 156 +++++++++--------- .../preprocessing/Path.scala | 25 ++- 2 files changed, 97 insertions(+), 84 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 1c50ce7868..62a67142a2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -374,6 +374,52 @@ public void multipleOptionalAppendSites(int value) { analyzeString(sb.toString()); } + @StringDefinitions( + value = "an extensive example with many control structures where appends follow " + + "after the read", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "(iv1|iv2): " + ) + public void extensiveEarlyRead(boolean cond) { + StringBuilder sb = new StringBuilder(); + if (cond) { + sb.append("iv1"); + } else { + sb.append("iv2"); + } + System.out.println(sb); + sb.append(": "); + + analyzeString(sb.toString()); + + Random random = new Random(); + while (random.nextFloat() > 5.) { + if (random.nextInt() % 2 == 0) { + sb.append("great!"); + } + } + + if (sb.indexOf("great!") > -1) { + sb.append(getRuntimeClassName()); + } + } + + @StringDefinitions( + value = "case with a nested loop where in the outer loop a StringBuilder is created " + + "that is later read", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b)*" + ) + public void nestedLoops(int range) { + for (int i = 0; i < range; i++) { + StringBuilder sb = new StringBuilder("a"); + for (int j = 0; j < range * range; j++) { + sb.append("b"); + } + analyzeString(sb.toString()); + } + } + // @StringDefinitions( // value = "an extensive example with many control structures", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, @@ -403,24 +449,24 @@ public void multipleOptionalAppendSites(int value) { // analyzeString(sb.toString()); // } -// @StringDefinitions( -// value = "a case with a switch with missing breaks", -// expectedLevel = StringConstancyLevel.CONSTANT, -// expectedStrings = "a(bc|c)?" -// ) -// public void switchWithMissingBreak(int value) { -// StringBuilder sb = new StringBuilder("a"); -// switch (value) { -// case 0: -// sb.append("b"); -// case 1: -// sb.append("c"); -// break; -// case 2: -// break; -// } -// analyzeString(sb.toString()); -// } + // @StringDefinitions( + // value = "a case with a switch with missing breaks", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(bc|c)?" + // ) + // public void switchWithMissingBreak(int value) { + // StringBuilder sb = new StringBuilder("a"); + // switch (value) { + // case 0: + // sb.append("b"); + // case 1: + // sb.append("c"); + // break; + // case 2: + // break; + // } + // analyzeString(sb.toString()); + // } // // @StringDefinitions( // // value = "checks if a string value with > 2 continuous appends and a second " @@ -438,36 +484,6 @@ public void multipleOptionalAppendSites(int value) { // // analyzeString(sb.toString()); // // } - // - // // @StringDefinitions( - // // value = "an extensive example with many control structures where appends follow " - // // + "after the read", - // // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // // expectedStrings = "(iv1|iv2): " - // // ) - // // public void extensiveEarlyRead(boolean cond) { - // // StringBuilder sb = new StringBuilder(); - // // if (cond) { - // // sb.append("iv1"); - // // } else { - // // sb.append("iv2"); - // // } - // // System.out.println(sb); - // // sb.append(": "); - // // - // // analyzeString(sb.toString()); - // // - // // Random random = new Random(); - // // while (random.nextFloat() > 5.) { - // // if (random.nextInt() % 2 == 0) { - // // sb.append("great!"); - // // } - // // } - // // - // // if (sb.indexOf("great!") > -1) { - // // sb.append(getRuntimeClassName()); - // // } - // // } // // // @StringDefinitions( // // value = "a case where a StringBuffer is used (instead of a StringBuilder)", @@ -498,35 +514,23 @@ public void multipleOptionalAppendSites(int value) { // // analyzeString(sb.toString()); // // } // - // // @StringDefinitions( - // // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " - // // + "Michael Eichberg)?", - // // expectedLevel = StringConstancyLevel.CONSTANT, - // // expectedStrings = "a(b)*" - // // ) - // // public void whileTrue() { - // // StringBuilder sb = new StringBuilder("a"); - // // while (true) { - // // sb.append("b"); - // // } - // // analyzeString(sb.toString()); - // // } + // @StringDefinitions( + // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " + // + "Michael Eichberg)?", + // expectedLevel = StringConstancyLevel.CONSTANT, + // expectedStrings = "a(b)*" + // ) + // public void whileTrue() { + // StringBuilder sb = new StringBuilder("a"); + // while (true) { + // sb.append("b"); + // if (sb.length() > 100) { + // break; + // } + // } + // analyzeString(sb.toString()); + // } // - // // @StringDefinitions( - // // value = "case with a nested loop where in the outer loop a StringBuilder is created " - // // + "that is later read (TODO: As Michael Eichberg meant?)", - // // expectedLevel = StringConstancyLevel.CONSTANT, - // // expectedStrings = "a(b)*" - // // ) - // // public void nestedLoops(int range) { - // // for (int i = 0; i < range; i++) { - // // StringBuilder sb = new StringBuilder("a"); - // // for (int j = 0; j < range * range; j++) { - // // sb.append("b"); - // // } - // // analyzeString(sb.toString()); - // // } - // // } // // // @StringDefinitions( // // value = "case with an exception", diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 6aa3e9d94d..db38f1a680 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -156,15 +156,24 @@ case class Path(elements: List[SubPath]) { // Transform the list into a map to have a constant access time val siteMap = Map(getAllDefAndUseSites(obj, stmts) map { s ⇒ (s, Unit) }: _*) val leanPath = ListBuffer[SubPath]() - elements.foreach { - case fpe: FlatPathElement if siteMap.contains(fpe.element) ⇒ - leanPath.append(fpe) - case npe: NestedPathElement ⇒ - val leanedPath = makeLeanPathAcc(npe, siteMap) - if (leanedPath.isDefined) { - leanPath.append(leanedPath.get) + val endSite = obj.definedBy.head + var reachedEndSite = false + elements.foreach { next ⇒ + if (!reachedEndSite) { + next match { + case fpe: FlatPathElement if siteMap.contains(fpe.element) ⇒ + leanPath.append(fpe) + if (fpe.element == endSite) { + reachedEndSite = true + } + case npe: NestedPathElement ⇒ + val leanedPath = makeLeanPathAcc(npe, siteMap) + if (leanedPath.isDefined) { + leanPath.append(leanedPath.get) + } + case _ ⇒ } - case _ ⇒ + } } if (elements.isEmpty) { From ef1e28d39edbc0f96103fc64c6b769b59ad67d60 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 21:53:39 +0100 Subject: [PATCH 048/316] Added support for StringBuffers and added an example test case. --- .../string_definition/TestMethods.java | 49 +++++++------------ .../LocalStringDefinitionAnalysis.scala | 2 +- .../InterpretationHandler.scala | 12 +++-- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 62a67142a2..e4567cfb9f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -420,6 +420,25 @@ public void nestedLoops(int range) { } } + @StringDefinitions( + value = "some example that makes use of a StringBuffer instead of a StringBuilder", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "((x|[AnIntegerValue]))*yz" + ) + public void stringBufferExample() { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 20; i++) { + if (i % 2 == 0) { + sb.append("x"); + } else { + sb.append(i + 1); + } + } + sb.append("yz"); + + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "an extensive example with many control structures", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, @@ -484,36 +503,6 @@ public void nestedLoops(int range) { // // analyzeString(sb.toString()); // // } - // - // // @StringDefinitions( - // // value = "a case where a StringBuffer is used (instead of a StringBuilder)", - // // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" - // // ) - // // public void extensiveStringBuffer(boolean cond) { - // // StringBuffer sb = new StringBuffer(); - // // if (cond) { - // // sb.append("iv1"); - // // } else { - // // sb.append("iv2"); - // // } - // // System.out.println(sb); - // // sb.append(": "); - // // - // // Random random = new Random(); - // // while (random.nextFloat() > 5.) { - // // if (random.nextInt() % 2 == 0) { - // // sb.append("great!"); - // // } - // // } - // // - // // if (sb.indexOf("great!") > -1) { - // // sb.append(getRuntimeClassName()); - // // } - // // - // // analyzeString(sb.toString()); - // // } - // // @StringDefinitions( // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " // + "Michael Eichberg)?", diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index a06691af5e..8c271a549b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -48,7 +48,7 @@ class LocalStringDefinitionAnalysis( val defSites = data._1.definedBy.toArray.sorted val expr = stmts(defSites.head).asAssignment.expr val pathFinder: AbstractPathFinder = new DefaultPathFinder() - if (InterpretationHandler.isStringBuilderToStringCall(expr)) { + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { val initDefSites = InterpretationHandler.findDefSiteOfInit( expr.asVirtualFunctionCall, stmts ) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index dce66ff076..466de12f5f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -119,15 +119,19 @@ object InterpretationHandler { def apply(cfg: CFG[Stmt[V], TACStmts[V]]): InterpretationHandler = new InterpretationHandler(cfg) /** - * Checks whether an expression contains a call to [[StringBuilder.toString]]. + * Checks whether an expression contains a call to [[StringBuilder#toString]] or + * [[StringBuffer#toString]]. * * @param expr The expression that is to be checked. - * @return Returns true if `expr` is a call to [[StringBuilder.toString]]. + * @return Returns true if `expr` is a call to `toString` of [[StringBuilder]] or + * [[StringBuffer]]. */ - def isStringBuilderToStringCall(expr: Expr[V]): Boolean = + def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ - clazz.toJavaClass.getName == "java.lang.StringBuilder" && name == "toString" + val className = clazz.toJavaClass.getName + (className == "java.lang.StringBuilder" || className == "java.lang.StringBuffer") && + name == "toString" case _ ⇒ false } From 24308b95551526838937aee3cca047f490b4169f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Nov 2018 22:07:09 +0100 Subject: [PATCH 049/316] Fixed a minor bug in the 'pathToStringTree'; now a while-true test case works --- .../string_definition/TestMethods.java | 34 +++++++++---------- .../preprocessing/PathTransformer.scala | 22 ++++++++---- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index e4567cfb9f..6bac450af6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -439,6 +439,22 @@ public void stringBufferExample() { analyzeString(sb.toString()); } + @StringDefinitions( + value = "while-true example", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b)*" + ) + public void whileTrueExample() { + StringBuilder sb = new StringBuilder("a"); + while (true) { + sb.append("b"); + if (sb.length() > 100) { + break; + } + } + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "an extensive example with many control structures", // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, @@ -503,24 +519,6 @@ public void stringBufferExample() { // // analyzeString(sb.toString()); // // } - // @StringDefinitions( - // value = "while-true example (how exactly is it supposed to look like (TODO: Talk to " - // + "Michael Eichberg)?", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(b)*" - // ) - // public void whileTrue() { - // StringBuilder sb = new StringBuilder("a"); - // while (true) { - // sb.append("b"); - // if (sb.length() > 100) { - // break; - // } - // } - // analyzeString(sb.toString()); - // } - // - // // // @StringDefinitions( // // value = "case with an exception", // // expectedLevel = StringConstancyLevel.CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 0eb52573c2..45bff966d1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -43,7 +43,11 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { case _ ⇒ val treeElements = ListBuffer[StringTree]() treeElements.appendAll(sciList.map(StringTreeConst).to[ListBuffer]) - Some(StringTreeOr(treeElements)) + if (treeElements.nonEmpty) { + Some(StringTreeOr(treeElements)) + } else { + None + } } case npe: NestedPathElement ⇒ if (npe.elementType.isDefined) { @@ -61,12 +65,16 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { val processedSubPaths = npe.element.map( pathToTreeAcc ).filter(_.isDefined).map(_.get) - npe.elementType.get match { - case NestedPathType.CondWithAlternative ⇒ - Some(StringTreeOr(processedSubPaths)) - case NestedPathType.CondWithoutAlternative ⇒ - Some(StringTreeCond(processedSubPaths)) - case _ ⇒ None + if (processedSubPaths.nonEmpty) { + npe.elementType.get match { + case NestedPathType.CondWithAlternative ⇒ + Some(StringTreeOr(processedSubPaths)) + case NestedPathType.CondWithoutAlternative ⇒ + Some(StringTreeCond(processedSubPaths)) + case _ ⇒ None + } + } else { + None } } } else { From 1f6a08a63f6c5860d3b94ccd54abe1b0fd7d4a1a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 12:16:12 +0100 Subject: [PATCH 050/316] Improvements on the VirtualFunctionCallInterpreter. --- .../VirtualFunctionCallInterpreter.scala | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 6bfe2ccc5f..bce3bb1369 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -56,33 +56,36 @@ class VirtualFunctionCallInterpreter( private def interpretAppendCall( appendCall: VirtualFunctionCall[V] ): List[StringConstancyInformation] = { - val receiverValue = receiverValueOfAppendCall(appendCall) + val receiverValues = receiverValuesOfAppendCall(appendCall) val appendValue = valueOfAppendCall(appendCall) - if (receiverValue.isEmpty && appendValue.isEmpty) { - List() - } else if (receiverValue.isDefined && appendValue.isEmpty) { - List(receiverValue.get) - } else if (receiverValue.isEmpty && appendValue.nonEmpty) { - List(appendValue.get) + // It might be that we have to go back as much as to a New expression. As they currently do + // not produce a result, the if part + if (receiverValues.isEmpty) { + List(appendValue) } else { - List(StringConstancyInformation( - StringConstancyLevel.determineForConcat( - receiverValue.get.constancyLevel, - appendValue.get.constancyLevel - ), - receiverValue.get.possibleStrings + appendValue.get.possibleStrings - )) + receiverValues.map { nextSci ⇒ + StringConstancyInformation( + StringConstancyLevel.determineForConcat( + nextSci.constancyLevel, appendValue.constancyLevel + ), + nextSci.possibleStrings + appendValue.possibleStrings + ) + } } } /** * This function determines the current value of the receiver object of an `append` call. */ - private def receiverValueOfAppendCall( + private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V] - ): Option[StringConstancyInformation] = - exprHandler.processDefSite(call.receiver.asVar.definedBy.head).headOption + ): List[StringConstancyInformation] = + // There might be several receivers, thus the map; from the processed sites, however, use + // only the head as a single receiver interpretation will produce one element + call.receiver.asVar.definedBy.toArray.sorted.map( + exprHandler.processDefSite + ).filter(_.nonEmpty).map(_.head).toList /** * Determines the (string) value that was passed to a `String{Builder, Buffer}#append` method. @@ -90,28 +93,28 @@ class VirtualFunctionCallInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V] - ): Option[StringConstancyInformation] = { + ): StringConstancyInformation = { + // .head because we want to evaluate only the first argument of append val value = exprHandler.processDefSite(call.params.head.asVar.definedBy.head) call.params.head.asVar.value.computationalType match { // For some types, we know the (dynamic) values - case ComputationalTypeInt ⇒ Some(StringConstancyInformation( + case ComputationalTypeInt ⇒ StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyInformation.IntValue - )) - case ComputationalTypeFloat ⇒ Some(StringConstancyInformation( + ) + case ComputationalTypeFloat ⇒ StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyInformation.FloatValue - )) + ) // Otherwise, try to compute case _ ⇒ // It might be necessary to merge the values of the receiver and of the parameter value.size match { - case 0 ⇒ None - case 1 ⇒ value.headOption - case _ ⇒ Some(StringConstancyInformation( + case 1 ⇒ value.head + case _ ⇒ StringConstancyInformation( StringConstancyLevel.determineForConcat( value.head.constancyLevel, value(1).constancyLevel ), value.head.possibleStrings + value(1).possibleStrings - )) + ) } } } From 5c9f5b6a37cb1049ca0b5dc19d007a5a26ce168e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 13:58:11 +0100 Subject: [PATCH 051/316] Reformatted and changed a comment. --- .../interpretation/AbstractStringInterpreter.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala index 9112f02358..0254dabba2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala @@ -15,8 +15,8 @@ import org.opalj.tac.TACStmts * @author Patrick Mell */ abstract class AbstractStringInterpreter( - protected val cfg: CFG[Stmt[V], TACStmts[V]], - protected val exprHandler: InterpretationHandler, + protected val cfg: CFG[Stmt[V], TACStmts[V]], + protected val exprHandler: InterpretationHandler, ) { type T <: Any @@ -26,11 +26,11 @@ abstract class AbstractStringInterpreter( * @param instr The instruction that is to be interpreted. It is the responsibility of * implementations to make sure that an instruction is properly and comprehensively * evaluated. - * @return The interpreted instruction. An empty list that an instruction was not / could not be - * interpreted (e.g., because it is not supported or it was processed before). A list - * with more than one element indicates an option (only one out of the values is - * possible during runtime of the program); thus, all concats must already happen within - * the interpretation. + * @return The interpreted instruction. An empty list indicates that an instruction was not / + * could not be interpreted (e.g., because it is not supported or it was processed + * before). A list with more than one element indicates an option (only one out of the + * values is possible during runtime of the program); thus, some concatenations must + * already happen within the interpretation. */ def interpret(instr: T): List[StringConstancyInformation] From 51d47af0f50983a804d6f5b5f4eccba9d14f8880 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 14:01:50 +0100 Subject: [PATCH 052/316] Fixed a typo. --- .../string_definition/interpretation/NewInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala index 9c43cc15c5..6b25c0766d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala @@ -25,7 +25,7 @@ class NewInterpreter( /** * [[New]] expressions do not carry any relevant information in this context (as the initial * values are not set in a [[New]] expressions but, e.g., in - * [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, thus implementation always returns an + * [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, this implementation always returns an * empty list. * * @see [[AbstractStringInterpreter.interpret]] From 9c2f267b366401401c8ee3c058dd05e623cfc027 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 14:02:06 +0100 Subject: [PATCH 053/316] Slightly reformatted the file. --- .../interpretation/NonVirtualMethodCallInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala index 0e04ccecba..4ccaa861ab 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala @@ -29,7 +29,7 @@ class NonVirtualMethodCallInterpreter( * Currently, this function supports the interpretation of the following non virtual methods: *
    *
  • - * `<init>`, when initializing an object (for this case, currently zero constructor or + * `<init>`, when initializing an object (for this case, currently zero constructor or * one constructor parameter are supported; if more params are available, only the very first * one is interpreted). *
  • From a2757f7e2e7d1f275b2e1c94b6e17046a5794666 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 28 Nov 2018 16:55:05 +0100 Subject: [PATCH 054/316] Improved the procedure to find all paths and updated the (working) test cases accordingly. --- .../string_definition/TestMethods.java | 78 ++++++++++++------- .../preprocessing/AbstractPathFinder.scala | 42 +++++++++- .../preprocessing/DefaultPathFinder.scala | 25 ++++-- 3 files changed, 109 insertions(+), 36 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 6bac450af6..78c4ed163b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -402,6 +402,8 @@ public void extensiveEarlyRead(boolean cond) { if (sb.indexOf("great!") > -1) { sb.append(getRuntimeClassName()); } + + // TODO: Noch ein Aufruf zu analyzeString } @StringDefinitions( @@ -444,7 +446,7 @@ public void stringBufferExample() { expectedLevel = StringConstancyLevel.CONSTANT, expectedStrings = "a(b)*" ) - public void whileTrueExample() { + public void whileWithBreak() { StringBuilder sb = new StringBuilder("a"); while (true) { sb.append("b"); @@ -455,34 +457,52 @@ public void whileTrueExample() { analyzeString(sb.toString()); } - // @StringDefinitions( - // value = "an extensive example with many control structures", - // expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - // expectedStrings = "(iv1|iv2): (great!)*(\\w)?" - // ) - // public void extensive(boolean cond) { - // StringBuilder sb = new StringBuilder(); - // if (cond) { - // sb.append("iv1"); - // } else { - // sb.append("iv2"); - // } - // System.out.println(sb); - // sb.append(": "); - // - // Random random = new Random(); - // while (random.nextFloat() > 5.) { - // if (random.nextInt() % 2 == 0) { - // sb.append("great!"); - // } - // } - // - // if (sb.indexOf("great!") > -1) { - // sb.append(getRuntimeClassName()); - // } - // - // analyzeString(sb.toString()); - // } + @StringDefinitions( + value = "an example with a non-while-true loop containing a break", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "a(b)*" + ) + public void whileWithBreak(int i) { + StringBuilder sb = new StringBuilder("a"); + int j = 0; + while (j < i) { + sb.append("b"); + if (sb.length() > 100) { + break; + } + j++; + } + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "an extensive example with many control structures", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + ) + public void extensive(boolean cond) { + StringBuilder sb = new StringBuilder(); + if (cond) { + sb.append("iv1"); + } else { + sb.append("iv2"); + } + System.out.println(sb); + sb.append(": "); + + Random random = new Random(); + while (random.nextFloat() > 5.) { + if (random.nextInt() % 2 == 0) { + sb.append("great!"); + } + } + + if (sb.indexOf("great!") > -1) { + sb.append(getRuntimeClassName()); + } + + analyzeString(sb.toString()); + } // @StringDefinitions( // value = "a case with a switch with missing breaks", diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 2923bb263a..2eab085843 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -4,6 +4,7 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import org.opalj.br.cfg.CFG import org.opalj.br.cfg.CFGNode import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.tac.If import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -36,9 +37,45 @@ trait AbstractPathFinder { * Determines whether a given `site` is the head of a loop by comparing it to a set of loops * (here a list of lists). This function returns ''true'', if `site` is the head of one of the * inner lists. + * Note that some high-level constructs, such as ''while-true'', might produce a loop where the + * check, whether to loop again or leave the loop, is placed at the end of the loop. In such + * cases, the very first statement of a loop is considered its head (which can be an assignment + * or function call not related to the loop header for instance). */ - protected def isHeadOfLoop(site: Int, loops: List[List[Int]]): Boolean = - loops.foldLeft(false)((old: Boolean, nextLoop: List[Int]) ⇒ old || nextLoop.head == site) + protected def isHeadOfLoop( + site: Int, loops: List[List[Int]], cfg: CFG[Stmt[V], TACStmts[V]] + ): Boolean = { + var belongsToLoopHeader = false + + // First, check the trivial case: Is the given site the first statement in a loop (covers, + // e.g., the above-mentioned while-true cases) + loops.foreach { loop ⇒ + if (!belongsToLoopHeader) { + if (loop.head == site) { + belongsToLoopHeader = true + } + } + } + + // The loop header might not only consist of the very first element in 'loops'; thus, check + // whether the given site is between the first site of a loop and the site of the very first + // if (again, respect structures as produces by while-true loops) + if (!belongsToLoopHeader) { + loops.foreach { nextLoop ⇒ + if (!belongsToLoopHeader) { + val start = nextLoop.head + var end = start + while (!cfg.code.instructions(end).isInstanceOf[If[V]]) { + end += 1 + } + if (site >= start && site <= end && end < nextLoop.last) { + belongsToLoopHeader = true + } + } + } + } + belongsToLoopHeader + } /** * Determines whether a given `site` is the end of a loop by comparing it to a set of loops @@ -103,6 +140,7 @@ trait AbstractPathFinder { * implementations to attach these information to [[NestedPathElement]]s (so that * procedures using results of this function do not need to re-process). */ + // TODO: endSite kann raus def findPaths(startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Path } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index fb29a4f3b4..f736deef6c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -31,19 +31,23 @@ class DefaultPathFinder extends AbstractPathFinder { ): Path = { // path will accumulate all paths val path = ListBuffer[SubPath]() + // TODO: Use Opal IntArrayStack val stack = ListBuffer[Int](startSites: _*) val seenElements = ListBuffer[Int]() // numSplits serves a queue that stores the number of possible branches (or successors) val numSplits = ListBuffer[Int]() // Also a queue that stores the indices of which branch of a conditional to take next val currSplitIndex = ListBuffer[Int]() + val numBackedgesLoop = ListBuffer[Int]() + val backedgeLoopCounter = ListBuffer[Int]() // Used to quickly find the element at which to insert a sub path val nestedElementsRef = ListBuffer[NestedPathElement]() val natLoops = cfg.findNaturalLoops() // Multiple start sites => We start within a conditional => Prepare for that if (startSites.size > 1) { - val outerNested = generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) + val outerNested = + generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) numSplits.append(startSites.size) currSplitIndex.append(0) nestedElementsRef.append(outerNested) @@ -54,9 +58,10 @@ class DefaultPathFinder extends AbstractPathFinder { val popped = stack.head stack.remove(0) val bb = cfg.bb(popped) - val isLoopHeader = isHeadOfLoop(popped, natLoops) + val isLoopHeader = isHeadOfLoop(popped, natLoops, cfg) var isLoopEnding = false var loopEndingIndex = -1 + var belongsToLoopEnding = false var belongsToLoopHeader = false // Append everything of the current basic block to the path @@ -69,9 +74,11 @@ class DefaultPathFinder extends AbstractPathFinder { } // For loop headers, insert a new nested element (and thus, do the housekeeping) - if (isHeadOfLoop(i, natLoops)) { + if (!belongsToLoopHeader && isHeadOfLoop(i, natLoops, cfg)) { numSplits.prepend(1) currSplitIndex.prepend(0) + numBackedgesLoop.prepend(bb.predecessors.size - 1) + backedgeLoopCounter.prepend(0) val outer = generateNestPathElement(0, NestedPathType.Repetition) outer.element.append(toAppend) @@ -88,9 +95,15 @@ class DefaultPathFinder extends AbstractPathFinder { } } if (loopElement.isDefined) { - loopEndingIndex = nestedElementsRef.indexOf(loopElement.get) + if (!belongsToLoopEnding) { + backedgeLoopCounter(0) += 1 + if (backedgeLoopCounter.head == numBackedgesLoop.head) { + loopEndingIndex = nestedElementsRef.indexOf(loopElement.get) + } + } loopElement.get.element.append(toAppend) } + belongsToLoopEnding = true } // The instructions belonging to a loop header are stored in a flat structure else if (!belongsToLoopHeader && (numSplits.isEmpty || bb.predecessors.size > 1)) { path.append(toAppend) @@ -123,6 +136,8 @@ class DefaultPathFinder extends AbstractPathFinder { numSplits.remove(loopEndingIndex) currSplitIndex.remove(loopEndingIndex) nestedElementsRef.remove(loopEndingIndex) + numBackedgesLoop.remove(0) + backedgeLoopCounter.remove(0) } // At the join point of a branching, do some housekeeping @@ -143,7 +158,7 @@ class DefaultPathFinder extends AbstractPathFinder { stack.appendAll(successorsToAdd) } // On a split point, prepare the next (nested) element (however, not for loop headers) - if (successors.length > 1 && !isLoopHeader) { + if (successorsToAdd.length > 1 && !isLoopHeader) { val appendSite = if (numSplits.isEmpty) path else nestedElementsRef(currSplitIndex.head).element var relevantNumSuccessors = successors.size From ef5402f10a1c85a1f6feb3425fa1e59f9571eb6c Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 09:39:55 +0100 Subject: [PATCH 055/316] Removed an unnecessary parameter. --- .../string_definition/LocalStringDefinitionAnalysis.scala | 4 ++-- .../string_definition/preprocessing/AbstractPathFinder.scala | 5 +---- .../string_definition/preprocessing/DefaultPathFinder.scala | 4 +--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 8c271a549b..0db00fb636 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -56,7 +56,7 @@ class LocalStringDefinitionAnalysis( throw new IllegalStateException("did not find any initializations!") } - val paths = pathFinder.findPaths(initDefSites, data._1.definedBy.head, cfg) + val paths = pathFinder.findPaths(initDefSites, cfg) val leanPaths = paths.makeLeanPath(data._1, stmts) // The following case should only occur if an object is queried that does not occur at // all within the CFG @@ -72,7 +72,7 @@ class LocalStringDefinitionAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val paths = pathFinder.findPaths(defSites.toList, data._1.definedBy.head, cfg) + val paths = pathFinder.findPaths(defSites.toList, cfg) if (paths.elements.isEmpty) { NoResult } else { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 2eab085843..d679987989 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -130,8 +130,6 @@ trait AbstractPathFinder { * * @param startSites A list of possible start sites, that is, initializations. Several start * sites denote that an object is initialized within a conditional. - * @param endSite An end site which an implementation might use to early-stop the procedure. - * This site can be the read operation of interest, for instance. * @param cfg The underlying control flow graph which servers as the basis to find the paths. * @return Returns all found paths as a [[Path]] object. That means, the return object is a flat * structure, however, captures all hierarchies and (nested) flows. Note that a @@ -140,7 +138,6 @@ trait AbstractPathFinder { * implementations to attach these information to [[NestedPathElement]]s (so that * procedures using results of this function do not need to re-process). */ - // TODO: endSite kann raus - def findPaths(startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Path + def findPaths(startSites: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Path } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index f736deef6c..f0d681f841 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -26,9 +26,7 @@ class DefaultPathFinder extends AbstractPathFinder { * * @see [[AbstractPathFinder.findPaths]] */ - override def findPaths( - startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]] - ): Path = { + override def findPaths(startSites: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Path = { // path will accumulate all paths val path = ListBuffer[SubPath]() // TODO: Use Opal IntArrayStack From be17a38f895b97639c30eb1022a16ddc6a199b2f Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 10:22:23 +0100 Subject: [PATCH 056/316] The DefaultPathFinder now makes use of an IntArrayStack. --- .../preprocessing/DefaultPathFinder.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index f0d681f841..42138e1c15 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -6,6 +6,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.br.cfg.CFG import org.opalj.br.cfg.ExitNode +import org.opalj.collection.mutable.IntArrayStack import scala.collection.mutable.ListBuffer @@ -29,8 +30,7 @@ class DefaultPathFinder extends AbstractPathFinder { override def findPaths(startSites: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Path = { // path will accumulate all paths val path = ListBuffer[SubPath]() - // TODO: Use Opal IntArrayStack - val stack = ListBuffer[Int](startSites: _*) + var stack = IntArrayStack.fromSeq(startSites.reverse) val seenElements = ListBuffer[Int]() // numSplits serves a queue that stores the number of possible branches (or successors) val numSplits = ListBuffer[Int]() @@ -53,8 +53,7 @@ class DefaultPathFinder extends AbstractPathFinder { } while (stack.nonEmpty) { - val popped = stack.head - stack.remove(0) + val popped = stack.pop() val bb = cfg.bb(popped) val isLoopHeader = isHeadOfLoop(popped, natLoops, cfg) var isLoopEnding = false @@ -149,11 +148,16 @@ class DefaultPathFinder extends AbstractPathFinder { } } - // Within a conditional, prepend in order to keep the correct order if (numSplits.nonEmpty && (bb.predecessors.size == 1)) { - stack.prependAll(successorsToAdd) + // Within a conditional, prepend in order to keep the correct order + val newStack = IntArrayStack.fromSeq(stack.reverse) + newStack.push(IntArrayStack.fromSeq(successorsToAdd.reverse)) + stack = newStack } else { - stack.appendAll(successorsToAdd) + // Otherwise, append (also retain the correct order) + val newStack = IntArrayStack.fromSeq(successorsToAdd.reverse) + newStack.push(IntArrayStack.fromSeq(stack.reverse)) + stack = newStack } // On a split point, prepare the next (nested) element (however, not for loop headers) if (successorsToAdd.length > 1 && !isLoopHeader) { From f9ab1a6664f1da6467a5cfebb3f84d7dc458e420 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:01:56 +0100 Subject: [PATCH 057/316] 1) Added support for static function calls. 2) Extended the valueOfAppendCall procedure as it did not always capture all possible values. --- .../InterpretationHandler.scala | 3 ++ .../StaticFunctionCallInterpreter.scala | 39 +++++++++++++++++++ .../VirtualFunctionCallInterpreter.scala | 14 +++++-- 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 466de12f5f..7d5e704222 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -12,6 +12,7 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.StringConst import org.opalj.tac.TACStmts @@ -60,6 +61,8 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { new NewInterpreter(cfg, this).interpret(expr.asNew) case Assignment(_, _, expr) if expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ new VirtualFunctionCallInterpreter(cfg, this).interpret(expr.asVirtualFunctionCall) + case Assignment(_, _, expr) if expr.isInstanceOf[StaticFunctionCall[V]] ⇒ + new StaticFunctionCallInterpreter(cfg, this).interpret(expr.asStaticFunctionCall) case Assignment(_, _, expr) if expr.isInstanceOf[BinaryExpr[V]] ⇒ new BinaryExprInterpreter(cfg, this).interpret(expr.asBinaryExpr) case Assignment(_, _, expr) if expr.isInstanceOf[NonVirtualFunctionCall[V]] ⇒ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala new file mode 100644 index 0000000000..65521406b4 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.tac.StaticFunctionCall + +/** + * The `StaticFunctionCallInterpreter` is responsible for processing [[StaticFunctionCall]]s. + * For supported method calls, see the documentation of the `interpret` function. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class StaticFunctionCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = StaticFunctionCall[V] + + /** + * Currently, [[StaticFunctionCall]]s are not supported. Thus, this function always returns a + * list with a single element consisting of [[StringConstancyLevel.DYNAMIC]] and + * [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, StringConstancyInformation.UnknownWordSymbol + )) + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index bce3bb1369..381519f5a6 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -59,8 +59,8 @@ class VirtualFunctionCallInterpreter( val receiverValues = receiverValuesOfAppendCall(appendCall) val appendValue = valueOfAppendCall(appendCall) - // It might be that we have to go back as much as to a New expression. As they currently do - // not produce a result, the if part + // It might be that we have to go back as much as to a New expression. As they do not + // produce a result (= empty list), the if part if (receiverValues.isEmpty) { List(appendValue) } else { @@ -95,7 +95,15 @@ class VirtualFunctionCallInterpreter( call: VirtualFunctionCall[V] ): StringConstancyInformation = { // .head because we want to evaluate only the first argument of append - val value = exprHandler.processDefSite(call.params.head.asVar.definedBy.head) + val defSiteParamHead = call.params.head.asVar.definedBy.head + var value = exprHandler.processDefSite(defSiteParamHead) + // If defSiteParamHead points to a New, value will be the empty list. In that case, process + // the first use site (which is the call + if (value.isEmpty) { + value = exprHandler.processDefSite( + cfg.code.instructions(defSiteParamHead).asAssignment.targetVar.usedBy.toArray.min + ) + } call.params.head.asVar.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ StringConstancyInformation( From db16908a915f3615fa4c00dedec5d7197b00dd92 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:22:34 +0100 Subject: [PATCH 058/316] Added support for exceptions. --- .../string_definition/TestMethods.java | 48 ++++++++++++------- .../preprocessing/AbstractPathFinder.scala | 9 +++- .../preprocessing/DefaultPathFinder.scala | 24 ++++++++-- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 78c4ed163b..43eb77808c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -4,6 +4,9 @@ import org.opalj.fpcf.properties.string_definition.StringConstancyLevel; import org.opalj.fpcf.properties.string_definition.StringDefinitions; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Random; /** @@ -504,6 +507,34 @@ public void extensive(boolean cond) { analyzeString(sb.toString()); } + @StringDefinitions( + value = "an example with a throw (and no try-catch-finally)", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "File Content:\\w" + ) + public void withThrow(String filename) throws IOException { + StringBuilder sb = new StringBuilder("File Content:"); + String data = new String(Files.readAllBytes(Paths.get(filename))); + sb.append(data); + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "case with a try-catch-except", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "File Content:(\\w)?" + ) + public void withException(String filename) { + StringBuilder sb = new StringBuilder("File Content:"); + try { + String data = new String(Files.readAllBytes(Paths.get(filename))); + sb.append(data); + } catch (Exception ignore) { + } finally { + analyzeString(sb.toString()); + } + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevel = StringConstancyLevel.CONSTANT, @@ -521,8 +552,8 @@ public void extensive(boolean cond) { // break; // } // analyzeString(sb.toString()); - // } + // } // // @StringDefinitions( // // value = "checks if a string value with > 2 continuous appends and a second " // // + "StringBuilder is determined correctly", @@ -537,22 +568,7 @@ public void extensive(boolean cond) { // // sb.append("String"); // // sb.append(sb2.toString()); // // analyzeString(sb.toString()); - // // } - // // @StringDefinitions( - // // value = "case with an exception", - // // expectedLevel = StringConstancyLevel.CONSTANT, - // // expectedStrings = "(File Content: |File Content: *)" - // // ) - // // public void withException(String filename) { - // // StringBuilder sb = new StringBuilder("File Content: "); - // // try { - // // String data = new String(Files.readAllBytes(Paths.get(filename))); - // // sb.append(data); - // // } catch (Exception ignore) { - // // } finally { - // // analyzeString(sb.toString()); - // // } // // } private String getRuntimeClassName() { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index d679987989..64801349ac 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing +import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG import org.opalj.br.cfg.CFGNode import org.opalj.fpcf.analyses.string_definition.V @@ -98,7 +99,13 @@ trait AbstractPathFinder { * ''else'' branch. */ def isCondWithoutElse(branchingSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { - val successors = cfg.bb(branchingSite).successors.map(_.nodeId).toArray.sorted + val successorBlocks = cfg.bb(branchingSite).successors + // CatchNode exists => Regard it as conditional without alternative + if (successorBlocks.exists(_.isInstanceOf[CatchNode])) { + return true + } + + val successors = successorBlocks.map(_.nodeId).toArray.sorted // Separate the last element from all previous ones val branches = successors.reverse.tail.reverse val lastEle = successors.last diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 42138e1c15..88d6d6d285 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing +import org.opalj.br.cfg.CatchNode import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -8,6 +9,7 @@ import org.opalj.br.cfg.CFG import org.opalj.br.cfg.ExitNode import org.opalj.collection.mutable.IntArrayStack +import scala.collection.mutable import scala.collection.mutable.ListBuffer /** @@ -32,6 +34,9 @@ class DefaultPathFinder extends AbstractPathFinder { val path = ListBuffer[SubPath]() var stack = IntArrayStack.fromSeq(startSites.reverse) val seenElements = ListBuffer[Int]() + // For storing the node IDs of all seen catch nodes (they are to be used only once, thus + // this store) + val seenCatchNodes = mutable.Map[Int, Unit.type]() // numSplits serves a queue that stores the number of possible branches (or successors) val numSplits = ListBuffer[Int]() // Also a queue that stores the indices of which branch of a conditional to take next @@ -118,9 +123,13 @@ class DefaultPathFinder extends AbstractPathFinder { } } + // Find all regular successors (excluding CatchNodes) val successors = bb.successors.filter { !_.isInstanceOf[ExitNode] - }.map(_.nodeId).toList.sorted + }.map(_.nodeId).filter(_ >= 0).toList.sorted + val catchSuccessors = bb.successors.filter { s ⇒ + s.isInstanceOf[CatchNode] && !seenCatchNodes.contains(s.nodeId) + } val successorsToAdd = successors.filter { next ⇒ !seenElements.contains(next) && !stack.contains(next) } @@ -159,15 +168,22 @@ class DefaultPathFinder extends AbstractPathFinder { newStack.push(IntArrayStack.fromSeq(stack.reverse)) stack = newStack } - // On a split point, prepare the next (nested) element (however, not for loop headers) - if (successorsToAdd.length > 1 && !isLoopHeader) { + // On a split point, prepare the next (nested) element (however, not for loop headers), + // this includes if a node has a catch node as successor + if ((successorsToAdd.length > 1 && !isLoopHeader) || catchSuccessors.nonEmpty) { val appendSite = if (numSplits.isEmpty) path else nestedElementsRef(currSplitIndex.head).element var relevantNumSuccessors = successors.size var ifWithElse = true if (isCondWithoutElse(popped, cfg)) { - relevantNumSuccessors -= 1 + // If there are catch node successors, the number of relevant successor equals + // the number of successors (because catch node are excluded here) + if (catchSuccessors.isEmpty) { + relevantNumSuccessors -= 1 + } else { + seenCatchNodes ++= catchSuccessors.map(n ⇒ (n.nodeId, Unit)) + } ifWithElse = false } val outerNested = generateNestPathElement( From 06cd38455950dc2c042864dcd46be6e2777bbe0a Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:22:53 +0100 Subject: [PATCH 059/316] Reformatted the file. --- .../properties/StringConstancyInformation.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 0549a01c00..462310c21a 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -5,7 +5,7 @@ package org.opalj.fpcf.string_definition.properties * @author Patrick Mell */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value, possibleStrings: String + constancyLevel: StringConstancyLevel.Value, possibleStrings: String ) object StringConstancyInformation { From 4aae55fbe25f8546b2eca45a38d85448fcda843d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 30 Nov 2018 20:23:07 +0100 Subject: [PATCH 060/316] Slightly changed a comment. --- .../interpretation/NonVirtualFunctionCallInterpreter.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala index 0177ab1043..b7295eeef0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala @@ -25,16 +25,15 @@ class NonVirtualFunctionCallInterpreter( override type T = NonVirtualFunctionCall[V] /** - * Currently, [[NonVirtualFunctionCall]] are not supported. Thus, this function always returns a - * list with a single element consisting of [[StringConstancyLevel.DYNAMIC]] and + * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns + * a list with a single element consisting of [[StringConstancyLevel.DYNAMIC]] and * [[StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { + override def interpret(instr: T): List[StringConstancyInformation] = List(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyInformation.UnknownWordSymbol )) - } } From 2c1404af879251f4d29a99f5e7c6e6da9f165788 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 3 Dec 2018 10:09:23 +0100 Subject: [PATCH 061/316] Extended the support for exceptions (now try-catch-finally's are supported as well). --- .../string_definition/TestMethods.java | 19 ++++++++++++++++++- .../preprocessing/AbstractPathFinder.scala | 2 +- .../preprocessing/DefaultPathFinder.scala | 12 ++++++++---- .../preprocessing/PathTransformer.scala | 9 ++++++++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 43eb77808c..9c62f8a671 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -520,7 +520,7 @@ public void withThrow(String filename) throws IOException { } @StringDefinitions( - value = "case with a try-catch-except", + value = "case with a try-finally exception", expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, expectedStrings = "File Content:(\\w)?" ) @@ -535,6 +535,23 @@ public void withException(String filename) { } } + @StringDefinitions( + value = "case with a try-catch-finally exception", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "=====(\\w|=====)" + ) + public void tryCatchFinally(String filename) { + StringBuilder sb = new StringBuilder("====="); + try { + String data = new String(Files.readAllBytes(Paths.get(filename))); + sb.append(data); + } catch (Exception ignore) { + sb.append("====="); + } finally { + analyzeString(sb.toString()); + } + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevel = StringConstancyLevel.CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 64801349ac..9d5d6e984d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -102,7 +102,7 @@ trait AbstractPathFinder { val successorBlocks = cfg.bb(branchingSite).successors // CatchNode exists => Regard it as conditional without alternative if (successorBlocks.exists(_.isInstanceOf[CatchNode])) { - return true + return false } val successors = successorBlocks.map(_.nodeId).toArray.sorted diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 88d6d6d285..ad3c3a2fd8 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -125,8 +125,13 @@ class DefaultPathFinder extends AbstractPathFinder { // Find all regular successors (excluding CatchNodes) val successors = bb.successors.filter { - !_.isInstanceOf[ExitNode] - }.map(_.nodeId).filter(_ >= 0).toList.sorted + case _: ExitNode ⇒ false + case cn: CatchNode ⇒ cn.catchType.isDefined + case _ ⇒ true + }.map { + case cn: CatchNode ⇒ cn.handlerPC + case s ⇒ s.nodeId + }.toList.sorted val catchSuccessors = bb.successors.filter { s ⇒ s.isInstanceOf[CatchNode] && !seenCatchNodes.contains(s.nodeId) } @@ -171,6 +176,7 @@ class DefaultPathFinder extends AbstractPathFinder { // On a split point, prepare the next (nested) element (however, not for loop headers), // this includes if a node has a catch node as successor if ((successorsToAdd.length > 1 && !isLoopHeader) || catchSuccessors.nonEmpty) { + seenCatchNodes ++= catchSuccessors.map(n ⇒ (n.nodeId, Unit)) val appendSite = if (numSplits.isEmpty) path else nestedElementsRef(currSplitIndex.head).element var relevantNumSuccessors = successors.size @@ -181,8 +187,6 @@ class DefaultPathFinder extends AbstractPathFinder { // the number of successors (because catch node are excluded here) if (catchSuccessors.isEmpty) { relevantNumSuccessors -= 1 - } else { - seenCatchNodes ++= catchSuccessors.map(n ⇒ (n.nodeId, Unit)) } ifWithElse = false } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 45bff966d1..c70e7c3b6d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -68,7 +68,14 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { if (processedSubPaths.nonEmpty) { npe.elementType.get match { case NestedPathType.CondWithAlternative ⇒ - Some(StringTreeOr(processedSubPaths)) + // In case there is only one element in the sub path, + // transform it into a conditional element (as there is no + // alternative) + if (processedSubPaths.tail.nonEmpty) { + Some(StringTreeOr(processedSubPaths)) + } else { + Some(StringTreeCond(processedSubPaths)) + } case NestedPathType.CondWithoutAlternative ⇒ Some(StringTreeCond(processedSubPaths)) case _ ⇒ None From 04268c5c5987f6ad58cb54718026ca47977d11b6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 3 Dec 2018 10:09:34 +0100 Subject: [PATCH 062/316] Fixed a condition. --- .../fpcf/analyses/string_definition/preprocessing/Path.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index db38f1a680..63cb778238 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -176,7 +176,7 @@ case class Path(elements: List[SubPath]) { } } - if (elements.isEmpty) { + if (leanPath.isEmpty) { None } else { Some(Path(leanPath.toList)) From 0acf23794e2c1153e493de19db521761671b6e70 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 3 Dec 2018 22:56:21 +0100 Subject: [PATCH 063/316] Added support for String{Builder,Buffer} clears. --- .../string_definition/TestMethods.java | 28 ++++ .../BinaryExprInterpreter.scala | 5 +- .../InterpretationHandler.scala | 3 + .../NonVirtualFunctionCallInterpreter.scala | 3 +- .../StaticFunctionCallInterpreter.scala | 3 +- .../StringConstInterpreter.scala | 3 +- .../VirtualFunctionCallInterpreter.scala | 7 +- .../VirtualMethodCallInterpreter.scala | 51 ++++++++ .../properties/StringConstancyProperty.scala | 3 +- .../StringConstancyInformation.scala | 5 +- .../properties/StringConstancyType.scala | 27 ++++ .../properties/StringTree.scala | 122 ++++++++++++++---- 12 files changed, 225 insertions(+), 35 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 9c62f8a671..3698c4c2d3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -552,6 +552,34 @@ public void tryCatchFinally(String filename) { } } + @StringDefinitions( + value = "a simple example with a StringBuilder#clear", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "\\w" + ) + public void simpleClearExample(int value) { + StringBuilder sb = new StringBuilder("init_value:"); + sb.setLength(0); + sb.append(getStringBuilderClassName()); + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "a more advanced example with a StringBuilder#clear", + expectedLevel = StringConstancyLevel.CONSTANT, + expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" + ) + public void advancedClearExample(int value) { + StringBuilder sb = new StringBuilder("init_value:"); + if (value < 10) { + sb.setLength(0); + } else { + sb.append("Hello, world!"); + } + sb.append("Goodbye"); + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevel = StringConstancyLevel.CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala index 27353b564b..442daeff01 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala @@ -9,6 +9,7 @@ import org.opalj.tac.Stmt import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND import org.opalj.tac.BinaryExpr /** @@ -40,10 +41,10 @@ class BinaryExprInterpreter( override def interpret(instr: T): List[StringConstancyInformation] = instr.cTpe match { case ComputationalTypeInt ⇒ List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, StringConstancyInformation.IntValue + StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.IntValue )) case ComputationalTypeFloat ⇒ List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, StringConstancyInformation.FloatValue + StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.FloatValue )) case _ ⇒ List() } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 7d5e704222..ceebc99314 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -17,6 +17,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.StringConst import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.VirtualMethodCall import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -75,6 +76,8 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { new VirtualFunctionCallInterpreter(cfg, this).interpret(vfc) case _ ⇒ List() } + case vmc: VirtualMethodCall[V] ⇒ + new VirtualMethodCallInterpreter(cfg, this).interpret(vmc) case nvmc: NonVirtualMethodCall[V] ⇒ new NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) case _ ⇒ List() diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala index b7295eeef0..e5ba51b892 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala @@ -5,6 +5,7 @@ import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -33,7 +34,7 @@ class NonVirtualFunctionCallInterpreter( */ override def interpret(instr: T): List[StringConstancyInformation] = List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, StringConstancyInformation.UnknownWordSymbol + StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.UnknownWordSymbol )) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala index 65521406b4..2bd90ca31a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala @@ -7,6 +7,7 @@ import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND import org.opalj.tac.StaticFunctionCall /** @@ -33,7 +34,7 @@ class StaticFunctionCallInterpreter( */ override def interpret(instr: T): List[StringConstancyInformation] = List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, StringConstancyInformation.UnknownWordSymbol + StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.UnknownWordSymbol )) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala index 2c45801515..ebd45fc777 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala @@ -5,6 +5,7 @@ import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND import org.opalj.tac.TACStmts import org.opalj.tac.Stmt import org.opalj.tac.StringConst @@ -30,6 +31,6 @@ class StringConstInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation(StringConstancyLevel.CONSTANT, instr.value)) + List(StringConstancyInformation(StringConstancyLevel.CONSTANT, APPEND, instr.value)) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 381519f5a6..2372e4a1c1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -9,6 +9,7 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND import org.opalj.tac.VirtualFunctionCall /** @@ -69,6 +70,7 @@ class VirtualFunctionCallInterpreter( StringConstancyLevel.determineForConcat( nextSci.constancyLevel, appendValue.constancyLevel ), + APPEND, nextSci.possibleStrings + appendValue.possibleStrings ) } @@ -107,10 +109,10 @@ class VirtualFunctionCallInterpreter( call.params.head.asVar.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ StringConstancyInformation( - StringConstancyLevel.DYNAMIC, StringConstancyInformation.IntValue + StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.IntValue ) case ComputationalTypeFloat ⇒ StringConstancyInformation( - StringConstancyLevel.DYNAMIC, StringConstancyInformation.FloatValue + StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.FloatValue ) // Otherwise, try to compute case _ ⇒ @@ -121,6 +123,7 @@ class VirtualFunctionCallInterpreter( StringConstancyLevel.determineForConcat( value.head.constancyLevel, value(1).constancyLevel ), + APPEND, value.head.possibleStrings + value(1).possibleStrings ) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala new file mode 100644 index 0000000000..8fe03de6e0 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala @@ -0,0 +1,51 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualMethodCall + +/** + * The `VirtualMethodCallInterpreter` is responsible for processing [[VirtualMethodCall]]s. + * For supported method calls, see the documentation of the `interpret` function. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class VirtualMethodCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = VirtualMethodCall[V] + + /** + * Currently, this function supports the interpretation of the following virtual methods: + *
      + *
    • + * `setLength`: `setLength` is a method to reset / clear a [[StringBuilder]] / [[StringBuffer]] + * (at least when called with the argument `0`). For simplicity, this interpreter currently + * assumes that 0 is always passed, i.e., the `setLength` method is currently always regarded as + * a reset mechanism. + *
    • + *
    + * For all other calls, an empty list will be returned. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + instr.name match { + case "setLength" ⇒ List(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.RESET + )) + case _ ⇒ List() + } + } + +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index e3682da87e..d5e2f09fae 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -10,6 +10,7 @@ import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.StringTreeConst @@ -39,7 +40,7 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases StringConstancyProperty(StringTreeConst( - StringConstancyInformation(DYNAMIC, "*") + StringConstancyInformation(DYNAMIC, StringConstancyType.APPEND, "*") )) }, (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 462310c21a..ad738d76e1 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -5,7 +5,10 @@ package org.opalj.fpcf.string_definition.properties * @author Patrick Mell */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value, possibleStrings: String + constancyLevel: StringConstancyLevel.Value, + constancyType: StringConstancyType.Value, + // Only relevant for some StringConstancyTypes + possibleStrings: String = "" ) object StringConstancyInformation { diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala new file mode 100644 index 0000000000..a8a2b27a92 --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.string_definition.properties + +/** + * Values in this enumeration represent the granularity of used strings. + * + * @author Patrick Mell + */ +object StringConstancyType extends Enumeration { + + type StringConstancyType = StringConstancyType.Value + + /** + * This type is to be used when a string value is appended to another (and also when a certain + * value represents an initialization, as an initialization can be seen as the concatenation + * of the empty string with the init value). + */ + final val APPEND = Value("append") + + /** + * This type is to be used when a string value is reset, that is, the string is set to the empty + * string (either by manually setting the value to the empty string or using a function like + * [[StringBuilder#delete]]). + */ + final val RESET = Value("reset") + +} diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index cf7dbd36c1..0b555dd6e4 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -20,49 +20,113 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * @param subtree The tree (or subtree) to reduce. * @return The reduced tree. */ - private def reduceAcc(subtree: StringTreeElement): StringConstancyInformation = { + private def reduceAcc(subtree: StringTreeElement): List[StringConstancyInformation] = { subtree match { case StringTreeRepetition(c, lowerBound, upperBound) ⇒ - val reduced = reduceAcc(c) + val reduced = reduceAcc(c).head val times = if (lowerBound.isDefined && upperBound.isDefined) (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol - StringConstancyInformation( + List(StringConstancyInformation( reduced.constancyLevel, + reduced.constancyType, s"(${reduced.possibleStrings})$times" - ) + )) case StringTreeConcat(cs) ⇒ - cs.map(reduceAcc).reduceLeft { (old, next) ⇒ - StringConstancyInformation( - StringConstancyLevel.determineForConcat( - old.constancyLevel, next.constancyLevel - ), - old.possibleStrings + next.possibleStrings - ) + val reducedLists = cs.map(reduceAcc) + val nestingLevel = reducedLists.foldLeft(0) { + (max: Int, next: List[StringConstancyInformation]) ⇒ Math.max(max, next.size) + } + val scis = ListBuffer[StringConstancyInformation]() + // Stores whether the last processed element was of type RESET + var wasReset = false + reducedLists.foreach { nextList ⇒ + nextList.foreach { nextSci ⇒ + // Add the first element only if not a reset (otherwise no new information) + if (scis.isEmpty && nextSci.constancyType != StringConstancyType.RESET) { + scis.append(nextSci) + } // No two consecutive resets (does not add any new information either) + else if (!wasReset || nextSci.constancyType != StringConstancyType.RESET) { + // A reset marks a new starting point => Add new element to the list + if (nextSci.constancyType == StringConstancyType.RESET) { + if (nestingLevel == 1) { + scis.clear() + } else { + scis.append(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.APPEND + )) + } + } // Otherwise, collapse / combine with seen elements + else { + scis.zipWithIndex.foreach { + case (sci, index) ⇒ + val collapsed = StringConstancyInformation( + StringConstancyLevel.determineForConcat( + sci.constancyLevel, nextSci.constancyLevel + ), + StringConstancyType.APPEND, + sci.possibleStrings + nextSci.possibleStrings + ) + scis(index) = collapsed + } + } + } + // For the next iteration + wasReset = nextSci.constancyType == StringConstancyType.RESET + } } + scis.toList case StringTreeOr(cs) ⇒ - val reduced = cs.map(reduceAcc).reduceLeft { (old, next) ⇒ - StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral( - old.constancyLevel, next.constancyLevel - ), - old.possibleStrings+"|"+next.possibleStrings - ) + val reduced = cs.flatMap(reduceAcc) + val containsReset = reduced.exists(_.constancyType == StringConstancyType.RESET) + val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } + val reducedInfo = appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, + s"${o.possibleStrings}|${n.possibleStrings}" + )) + + val scis = ListBuffer[StringConstancyInformation]() + var possibleStrings = s"${reducedInfo.possibleStrings}" + if (appendElements.tail.nonEmpty) { + possibleStrings = s"($possibleStrings)" + } + scis.append(StringConstancyInformation( + reducedInfo.constancyLevel, reducedInfo.constancyType, possibleStrings + )) + if (containsReset) { + scis.append(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.RESET + )) } - StringConstancyInformation(reduced.constancyLevel, s"(${reduced.possibleStrings})") + scis.toList case StringTreeCond(c) ⇒ - val scis = c.map(reduceAcc) - val reducedInfo = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( + val reduced = c.flatMap(reduceAcc) + val containsReset = reduced.exists(_.constancyType == StringConstancyType.RESET) + val reducedInfo = reduced.filter { + _.constancyType == StringConstancyType.APPEND + }.reduceLeft((o, n) ⇒ StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, s"${o.possibleStrings}|${n.possibleStrings}" )) - StringConstancyInformation( - reducedInfo.constancyLevel, s"(${reducedInfo.possibleStrings})?" - ) - case StringTreeConst(sci) ⇒ sci + val scis = ListBuffer[StringConstancyInformation]() + scis.append(StringConstancyInformation( + reducedInfo.constancyLevel, + reducedInfo.constancyType, + s"(${reducedInfo.possibleStrings})?" + )) + if (containsReset) { + scis.append(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.APPEND + )) + } + scis.toList + + case StringTreeConst(sci) ⇒ List(sci) } } @@ -190,7 +254,13 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * * @return A [[StringConstancyInformation]] instance that flatly describes this tree. */ - def reduce(): StringConstancyInformation = reduceAcc(this) + def reduce(): StringConstancyInformation = { + reduceAcc(this).reduceLeft((o, n) ⇒ StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, + s"(${o.possibleStrings}|${n.possibleStrings})" + )) + } /** * Simplifies this tree. Currently, this means that when a (sub) tree has a From 05b2a7cb7cd2480dede0cf5b9d8e1be15684d07d Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 10:25:10 +0100 Subject: [PATCH 064/316] Reformatted and refactored code belonging to the processing of String clear operations. --- .../BinaryExprInterpreter.scala | 13 +- .../InterpretationHandler.scala | 26 ++ .../NonVirtualFunctionCallInterpreter.scala | 10 +- .../StaticFunctionCallInterpreter.scala | 10 +- .../StringConstInterpreter.scala | 11 +- .../VirtualFunctionCallInterpreter.scala | 16 +- .../properties/StringConstancyProperty.scala | 10 +- .../StringConstancyInformation.scala | 12 +- .../properties/StringConstancyType.scala | 5 +- .../properties/StringTree.scala | 230 +++++++++--------- 10 files changed, 187 insertions(+), 156 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala index 442daeff01..835af36fa6 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala @@ -8,8 +8,6 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.tac.Stmt import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND import org.opalj.tac.BinaryExpr /** @@ -40,12 +38,11 @@ class BinaryExprInterpreter( */ override def interpret(instr: T): List[StringConstancyInformation] = instr.cTpe match { - case ComputationalTypeInt ⇒ List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.IntValue - )) - case ComputationalTypeFloat ⇒ List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.FloatValue - )) + case ComputationalTypeInt ⇒ + List(InterpretationHandler.getStringConstancyInformationForInt) + case ComputationalTypeFloat ⇒ + List(InterpretationHandler.getStringConstancyInformationForFloat) + case _ ⇒ List() } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index ceebc99314..bff106ab79 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -4,6 +4,8 @@ package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr @@ -178,4 +180,28 @@ object InterpretationHandler { defSites.sorted.toList } + /** + * @return Returns a [[StringConstancyInformation]] element that describes an `int` value. + * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]], and [[StringConstancyInformation.IntValue]]. + */ + def getStringConstancyInformationForInt: StringConstancyInformation = + StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.IntValue + ) + + /** + * @return Returns a [[StringConstancyInformation]] element that describes a `float` value. + * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]], and [[StringConstancyInformation.IntValue]]. + */ + def getStringConstancyInformationForFloat: StringConstancyInformation = + StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.FloatValue + ) + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala index e5ba51b892..1d11e3895c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala @@ -5,7 +5,7 @@ import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND +import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -27,14 +27,16 @@ class NonVirtualFunctionCallInterpreter( /** * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns - * a list with a single element consisting of [[StringConstancyLevel.DYNAMIC]] and - * [[StringConstancyInformation.UnknownWordSymbol]]. + * a list with a single element consisting of [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T): List[StringConstancyInformation] = List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.UnknownWordSymbol + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol )) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala index 2bd90ca31a..53ac9507c9 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala @@ -7,7 +7,7 @@ import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND +import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.tac.StaticFunctionCall /** @@ -27,14 +27,16 @@ class StaticFunctionCallInterpreter( /** * Currently, [[StaticFunctionCall]]s are not supported. Thus, this function always returns a - * list with a single element consisting of [[StringConstancyLevel.DYNAMIC]] and - * [[StringConstancyInformation.UnknownWordSymbol]]. + * list with a single element consisting of [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T): List[StringConstancyInformation] = List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.UnknownWordSymbol + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol )) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala index ebd45fc777..b873bf24c3 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala @@ -5,7 +5,7 @@ import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND +import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.tac.TACStmts import org.opalj.tac.Stmt import org.opalj.tac.StringConst @@ -26,11 +26,16 @@ class StringConstInterpreter( /** * The interpretation of a [[StringConst]] always results in a list with one - * [[StringConstancyInformation]] element. + * [[StringConstancyLevel.CONSTANT]] [[StringConstancyInformation]] element holding the + * stringified value. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation(StringConstancyLevel.CONSTANT, APPEND, instr.value)) + List(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value + )) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 2372e4a1c1..d51b621bf4 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -9,7 +9,7 @@ import org.opalj.br.ComputationalTypeInt import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType.APPEND +import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.tac.VirtualFunctionCall /** @@ -70,7 +70,7 @@ class VirtualFunctionCallInterpreter( StringConstancyLevel.determineForConcat( nextSci.constancyLevel, appendValue.constancyLevel ), - APPEND, + StringConstancyType.APPEND, nextSci.possibleStrings + appendValue.possibleStrings ) } @@ -108,12 +108,10 @@ class VirtualFunctionCallInterpreter( } call.params.head.asVar.value.computationalType match { // For some types, we know the (dynamic) values - case ComputationalTypeInt ⇒ StringConstancyInformation( - StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.IntValue - ) - case ComputationalTypeFloat ⇒ StringConstancyInformation( - StringConstancyLevel.DYNAMIC, APPEND, StringConstancyInformation.FloatValue - ) + case ComputationalTypeInt ⇒ + InterpretationHandler.getStringConstancyInformationForInt + case ComputationalTypeFloat ⇒ + InterpretationHandler.getStringConstancyInformationForFloat // Otherwise, try to compute case _ ⇒ // It might be necessary to merge the values of the receiver and of the parameter @@ -123,7 +121,7 @@ class VirtualFunctionCallInterpreter( StringConstancyLevel.determineForConcat( value.head.constancyLevel, value(1).constancyLevel ), - APPEND, + StringConstancyType.APPEND, value.head.possibleStrings + value(1).possibleStrings ) } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index d5e2f09fae..2c0a2f7b26 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -9,7 +9,7 @@ import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.StringTreeConst @@ -39,9 +39,11 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { PropertyKeyName, (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - StringConstancyProperty(StringTreeConst( - StringConstancyInformation(DYNAMIC, StringConstancyType.APPEND, "*") - )) + StringConstancyProperty(StringTreeConst(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + ))) }, (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, (_: PropertyStore, _: Entity) ⇒ None diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index ad738d76e1..1eabab8299 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -2,15 +2,19 @@ package org.opalj.fpcf.string_definition.properties /** + * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this + * parameter can be omitted. * @author Patrick Mell */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value, - constancyType: StringConstancyType.Value, - // Only relevant for some StringConstancyTypes - possibleStrings: String = "" + constancyLevel: StringConstancyLevel.Value, + constancyType: StringConstancyType.Value, + possibleStrings: String = "" ) +/** + * Provides a collection of instance-independent but string-constancy related values. + */ object StringConstancyInformation { /** diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala index a8a2b27a92..66d266be78 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala @@ -2,7 +2,8 @@ package org.opalj.fpcf.string_definition.properties /** - * Values in this enumeration represent the granularity of used strings. + * Values in this enumeration represent how a string / string container, such as [[StringBuilder]], + * are changed. * * @author Patrick Mell */ @@ -20,7 +21,7 @@ object StringConstancyType extends Enumeration { /** * This type is to be used when a string value is reset, that is, the string is set to the empty * string (either by manually setting the value to the empty string or using a function like - * [[StringBuilder#delete]]). + * `StringBuilder.setLength(0)`). */ final val RESET = Value("reset") diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 0b555dd6e4..5717ccbe77 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -4,7 +4,6 @@ package org.opalj.fpcf.string_definition.properties import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.InfiniteRepetitionSymbol import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ListBuffer /** @@ -14,118 +13,130 @@ import scala.collection.mutable.ListBuffer */ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeElement]) { + /** + * This is a helper function which processes the `reduce` operation for [[StringTreeOr]] and + * [[StringTreeCond]] elements (as both are processed in a very similar fashion). `children` + * denotes the children of the [[StringTreeOr]] or [[StringTreeCond]] element and `processOr` + * defines whether to process [[StringTreeOr]] or [[StringTreeCond]] (the latter in case `false` + * is passed). + */ + private def processReduceCondOrReduceOr( + children: List[StringTreeElement], processOr: Boolean = true + ): List[StringConstancyInformation] = { + val reduced = children.flatMap(reduceAcc) + val containsReset = reduced.exists(_.constancyType == StringConstancyType.RESET) + val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } + val reducedInfo = appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, + s"${o.possibleStrings}|${n.possibleStrings}" + )) + val scis = ListBuffer[StringConstancyInformation]() + + // The only difference between a Cond and an Or is how the possible strings look like + var possibleStrings = s"${reducedInfo.possibleStrings}" + if (processOr) { + if (appendElements.tail.nonEmpty) { + possibleStrings = s"($possibleStrings)" + } + } else { + possibleStrings = s"(${reducedInfo.possibleStrings})?" + } + + scis.append(StringConstancyInformation( + reducedInfo.constancyLevel, reducedInfo.constancyType, possibleStrings + )) + if (containsReset) { + scis.append(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.RESET + )) + } + scis.toList + } + + /** + * This is a helper function which processes the `reduce` operation for [[StringTreeConcat]] + * elements. + */ + private def processReduceConcat( + children: List[StringTreeElement] + ): List[StringConstancyInformation] = { + val reducedLists = children.map(reduceAcc) + // Stores whether we deal with a flat structure or with a nested structure (in the latter + // case maxNestingLevel >= 2) + val maxNestingLevel = reducedLists.foldLeft(0) { + (max: Int, next: List[StringConstancyInformation]) ⇒ Math.max(max, next.size) + } + val scis = ListBuffer[StringConstancyInformation]() + // Stores whether the last processed element was of type RESET + var wasReset = false + + reducedLists.foreach { nextSciList ⇒ + nextSciList.foreach { nextSci ⇒ + // Add the first element only if not a reset (otherwise no new information) + if (scis.isEmpty && nextSci.constancyType != StringConstancyType.RESET) { + scis.append(nextSci) + } // No two consecutive resets (as that does not add any new information either) + else if (!wasReset || nextSci.constancyType != StringConstancyType.RESET) { + // A reset marks a new starting point + if (nextSci.constancyType == StringConstancyType.RESET) { + // maxNestingLevel == 1 corresponds to n consecutive append call, i.e., + // clear everything that has been seen so far + if (maxNestingLevel == 1) { + scis.clear() + } // maxNestingLevel >= 2 corresponds to a new starting point (e.g., a clear + // in a if-else construction) => Add a new element + else { + scis.append(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.APPEND + )) + } + } // Otherwise, collapse / combine with previous elements + else { + scis.zipWithIndex.foreach { + case (innerSci, index) ⇒ + val collapsed = StringConstancyInformation( + StringConstancyLevel.determineForConcat( + innerSci.constancyLevel, nextSci.constancyLevel + ), + StringConstancyType.APPEND, + innerSci.possibleStrings + nextSci.possibleStrings + ) + scis(index) = collapsed + } + } + } + // For the next iteration + wasReset = nextSci.constancyType == StringConstancyType.RESET + } + } + scis.toList + } + /** * Accumulator / helper function for reducing a tree. * - * @param subtree The tree (or subtree) to reduce. - * @return The reduced tree. + * @param subtree The (sub) tree to reduce. + * @return The reduced tree in the form of a list of [[StringConstancyInformation]]. That is, if + * different [[StringConstancyType]]s occur, a single StringConstancyInformation element + * is not sufficient to describe the string approximation for this function. For + * example, a [[StringConstancyType.RESET]] marks the beginning of a new string + * alternative which results in a new list element. */ private def reduceAcc(subtree: StringTreeElement): List[StringConstancyInformation] = { subtree match { case StringTreeRepetition(c, lowerBound, upperBound) ⇒ - val reduced = reduceAcc(c).head val times = if (lowerBound.isDefined && upperBound.isDefined) (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol + val reduced = reduceAcc(c).head List(StringConstancyInformation( reduced.constancyLevel, reduced.constancyType, s"(${reduced.possibleStrings})$times" )) - - case StringTreeConcat(cs) ⇒ - val reducedLists = cs.map(reduceAcc) - val nestingLevel = reducedLists.foldLeft(0) { - (max: Int, next: List[StringConstancyInformation]) ⇒ Math.max(max, next.size) - } - val scis = ListBuffer[StringConstancyInformation]() - // Stores whether the last processed element was of type RESET - var wasReset = false - reducedLists.foreach { nextList ⇒ - nextList.foreach { nextSci ⇒ - // Add the first element only if not a reset (otherwise no new information) - if (scis.isEmpty && nextSci.constancyType != StringConstancyType.RESET) { - scis.append(nextSci) - } // No two consecutive resets (does not add any new information either) - else if (!wasReset || nextSci.constancyType != StringConstancyType.RESET) { - // A reset marks a new starting point => Add new element to the list - if (nextSci.constancyType == StringConstancyType.RESET) { - if (nestingLevel == 1) { - scis.clear() - } else { - scis.append(StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.APPEND - )) - } - } // Otherwise, collapse / combine with seen elements - else { - scis.zipWithIndex.foreach { - case (sci, index) ⇒ - val collapsed = StringConstancyInformation( - StringConstancyLevel.determineForConcat( - sci.constancyLevel, nextSci.constancyLevel - ), - StringConstancyType.APPEND, - sci.possibleStrings + nextSci.possibleStrings - ) - scis(index) = collapsed - } - } - } - // For the next iteration - wasReset = nextSci.constancyType == StringConstancyType.RESET - } - } - scis.toList - - case StringTreeOr(cs) ⇒ - val reduced = cs.flatMap(reduceAcc) - val containsReset = reduced.exists(_.constancyType == StringConstancyType.RESET) - val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } - val reducedInfo = appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - StringConstancyType.APPEND, - s"${o.possibleStrings}|${n.possibleStrings}" - )) - - val scis = ListBuffer[StringConstancyInformation]() - var possibleStrings = s"${reducedInfo.possibleStrings}" - if (appendElements.tail.nonEmpty) { - possibleStrings = s"($possibleStrings)" - } - scis.append(StringConstancyInformation( - reducedInfo.constancyLevel, reducedInfo.constancyType, possibleStrings - )) - if (containsReset) { - scis.append(StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.RESET - )) - } - scis.toList - - case StringTreeCond(c) ⇒ - val reduced = c.flatMap(reduceAcc) - val containsReset = reduced.exists(_.constancyType == StringConstancyType.RESET) - val reducedInfo = reduced.filter { - _.constancyType == StringConstancyType.APPEND - }.reduceLeft((o, n) ⇒ StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - StringConstancyType.APPEND, - s"${o.possibleStrings}|${n.possibleStrings}" - )) - - val scis = ListBuffer[StringConstancyInformation]() - scis.append(StringConstancyInformation( - reducedInfo.constancyLevel, - reducedInfo.constancyType, - s"(${reducedInfo.possibleStrings})?" - )) - if (containsReset) { - scis.append(StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.APPEND - )) - } - scis.toList - + case StringTreeConcat(cs) ⇒ processReduceConcat(cs.toList) + case StringTreeOr(cs) ⇒ processReduceCondOrReduceOr(cs.toList) + case StringTreeCond(cs) ⇒ processReduceCondOrReduceOr(cs.toList, processOr = false) case StringTreeConst(sci) ⇒ List(sci) } } @@ -255,6 +266,8 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * @return A [[StringConstancyInformation]] instance that flatly describes this tree. */ def reduce(): StringConstancyInformation = { + // The reduceLeft is necessary as reduceAcc might return a list, e.g., a clear occurred. In + // such cases, concatenate the values by or-ing them. reduceAcc(this).reduceLeft((o, n) ⇒ StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), StringConstancyType.APPEND, @@ -291,25 +304,6 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme */ def groupRepetitionElements(): StringTree = groupRepetitionElementsAcc(this) - /** - * @return Returns all leaf elements of this instance. - */ - def getLeafs: Array[StringTreeConst] = { - def leafsAcc(root: StringTreeElement, leafs: ArrayBuffer[StringTreeConst]): Unit = { - root match { - case StringTreeRepetition(c, _, _) ⇒ leafsAcc(c, leafs) - case StringTreeConcat(c) ⇒ c.foreach(leafsAcc(_, leafs)) - case StringTreeOr(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) - case StringTreeCond(cs) ⇒ cs.foreach(leafsAcc(_, leafs)) - case stc: StringTreeConst ⇒ leafs.append(stc) - } - } - - val leafs = ArrayBuffer[StringTreeConst]() - leafsAcc(this, leafs) - leafs.toArray - } - } /** From e6040ca9d8e097a98f6252236e0f238b56866bd6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 10:55:55 +0100 Subject: [PATCH 065/316] Added another test case for the 'clear string' case. --- .../string_definition/TestMethods.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 3698c4c2d3..77d66c6fdf 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -553,11 +553,11 @@ public void tryCatchFinally(String filename) { } @StringDefinitions( - value = "a simple example with a StringBuilder#clear", + value = "a simple example with a StringBuilder#setLength to clear it", expectedLevel = StringConstancyLevel.DYNAMIC, expectedStrings = "\\w" ) - public void simpleClearExample(int value) { + public void simpleClearWithSetLengthExample(int value) { StringBuilder sb = new StringBuilder("init_value:"); sb.setLength(0); sb.append(getStringBuilderClassName()); @@ -565,11 +565,24 @@ public void simpleClearExample(int value) { } @StringDefinitions( - value = "a more advanced example with a StringBuilder#clear", + value = "a simple example with a StringBuilder#new to clear it", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "\\w" + ) + public void simpleClearWithNewExample(int value) { + StringBuilder sb = new StringBuilder("init_value:"); + System.out.println(sb.toString()); + sb = new StringBuilder(); + sb.append(getStringBuilderClassName()); + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "a more advanced example with a StringBuilder#setLength to clear it", expectedLevel = StringConstancyLevel.CONSTANT, expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" ) - public void advancedClearExample(int value) { + public void advancedClearExampleWithSetLength(int value) { StringBuilder sb = new StringBuilder("init_value:"); if (value < 10) { sb.setLength(0); From 958de33c0c768ffafb5fc19fbcdf889251282a1d Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 20:16:09 +0100 Subject: [PATCH 066/316] Fixed a bug. --- .../string_definition/properties/StringConstancyLevel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala index 2b9033110b..e82feac3d5 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala @@ -42,7 +42,7 @@ object StringConstancyLevel extends Enumeration { ): StringConstancyLevel = { if (level1 == DYNAMIC || level2 == DYNAMIC) { DYNAMIC - } else if (level1 == PARTIALLY_CONSTANT && level2 == PARTIALLY_CONSTANT) { + } else if (level1 == PARTIALLY_CONSTANT || level2 == PARTIALLY_CONSTANT) { PARTIALLY_CONSTANT } else { CONSTANT From e98791d718cecfce13cae6cbfee6cdd1301274e7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 4 Dec 2018 20:17:01 +0100 Subject: [PATCH 067/316] The string definition analysis now supports 'replace' calls of String{Builder, Buffer} as well. --- .../string_definition/TestMethods.java | 27 +++++++++++++++++ .../InterpretationHandler.scala | 13 +++++++++ .../VirtualFunctionCallInterpreter.scala | 16 ++++++++++ .../properties/StringConstancyType.scala | 6 ++++ .../properties/StringTree.scala | 29 ++++++++++++------- 5 files changed, 80 insertions(+), 11 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 77d66c6fdf..15d188faa2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -593,6 +593,33 @@ public void advancedClearExampleWithSetLength(int value) { analyzeString(sb.toString()); } + @StringDefinitions( + value = "a simple example with a StringBuilder#replace call", + expectedLevel = StringConstancyLevel.DYNAMIC, + expectedStrings = "\\w" + ) + public void simpleReplaceExample() { + StringBuilder sb = new StringBuilder("init_value"); + sb.replace(0, 5, "replaced_value"); + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "a more advanced example with a StringBuilder#replace call", + expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, + expectedStrings = "(init_value:Hello, world!Goodbye|\\wGoodbye)" + ) + public void advancedReplaceExample(int value) { + StringBuilder sb = new StringBuilder("init_value:"); + if (value < 10) { + sb.replace(0, value, "..."); + } else { + sb.append("Hello, world!"); + } + sb.append("Goodbye"); + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevel = StringConstancyLevel.CONSTANT, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index bff106ab79..2d30014f94 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -204,4 +204,17 @@ object InterpretationHandler { StringConstancyInformation.FloatValue ) + /** + * @return Returns a [[StringConstancyInformation]] element that describes a the result of a + * `replace` operation. That is, the returned element currently consists of the value + * [[StringConstancyLevel.DYNAMIC]], [[StringConstancyType.REPLACE]], and + * [[StringConstancyInformation.UnknownWordSymbol]]. + */ + def getStringConstancyInformationForReplace: StringConstancyInformation = + StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.REPLACE, + StringConstancyInformation.UnknownWordSymbol + ) + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index d51b621bf4..5bbe832858 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -37,6 +37,10 @@ class VirtualFunctionCallInterpreter( * a `toString` call does not change the state of such an object, an empty list will be * returned. * + *
  • + * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For + * further information how this operation is processed, see + * [[VirtualFunctionCallInterpreter.interpretReplaceCall]]. *
* * @see [[AbstractStringInterpreter.interpret]] @@ -45,6 +49,7 @@ class VirtualFunctionCallInterpreter( instr.name match { case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) + case "replace" ⇒ interpretReplaceCall(instr) case _ ⇒ List() } } @@ -138,4 +143,15 @@ class VirtualFunctionCallInterpreter( ): List[StringConstancyInformation] = exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + /** + * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. + * Currently, this function simply approximates `replace` functions by returning a list with one + * element - the element currently is provided by + * [[InterpretationHandler.getStringConstancyInformationForReplace]]. + */ + private def interpretReplaceCall( + instr: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = + List(InterpretationHandler.getStringConstancyInformationForReplace) + } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala index 66d266be78..87841c687a 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala @@ -25,4 +25,10 @@ object StringConstancyType extends Enumeration { */ final val RESET = Value("reset") + /** + * This type is to be used when a string or part of a string is replaced by another string + * (e.g., when calling the `replace` method of [[StringBuilder]]). + */ + final val REPLACE = Value("replace") + } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 5717ccbe77..21bde1973a 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -24,7 +24,8 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme children: List[StringTreeElement], processOr: Boolean = true ): List[StringConstancyInformation] = { val reduced = children.flatMap(reduceAcc) - val containsReset = reduced.exists(_.constancyType == StringConstancyType.RESET) + val resetElement = reduced.find(_.constancyType == StringConstancyType.RESET) + val replaceElement = reduced.find(_.constancyType == StringConstancyType.REPLACE) val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } val reducedInfo = appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), @@ -46,10 +47,11 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme scis.append(StringConstancyInformation( reducedInfo.constancyLevel, reducedInfo.constancyType, possibleStrings )) - if (containsReset) { - scis.append(StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.RESET - )) + if (resetElement.isDefined) { + scis.append(resetElement.get) + } + if (replaceElement.isDefined) { + scis.append(replaceElement.get) } scis.toList } @@ -78,18 +80,22 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme scis.append(nextSci) } // No two consecutive resets (as that does not add any new information either) else if (!wasReset || nextSci.constancyType != StringConstancyType.RESET) { - // A reset marks a new starting point - if (nextSci.constancyType == StringConstancyType.RESET) { + // A reset / replace marks a new starting point + val isReset = nextSci.constancyType == StringConstancyType.RESET + val isReplace = nextSci.constancyType == StringConstancyType.REPLACE + if (isReset || isReplace) { // maxNestingLevel == 1 corresponds to n consecutive append call, i.e., // clear everything that has been seen so far if (maxNestingLevel == 1) { scis.clear() + // In case of replace, add the replace element + if (isReplace) { + scis.append(nextSci) + } } // maxNestingLevel >= 2 corresponds to a new starting point (e.g., a clear // in a if-else construction) => Add a new element else { - scis.append(StringConstancyInformation( - StringConstancyLevel.CONSTANT, StringConstancyType.APPEND - )) + scis.append(nextSci) } } // Otherwise, collapse / combine with previous elements else { @@ -268,7 +274,8 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme def reduce(): StringConstancyInformation = { // The reduceLeft is necessary as reduceAcc might return a list, e.g., a clear occurred. In // such cases, concatenate the values by or-ing them. - reduceAcc(this).reduceLeft((o, n) ⇒ StringConstancyInformation( + val reduced = reduceAcc(this) + reduced.reduceLeft((o, n) ⇒ StringConstancyInformation( StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), StringConstancyType.APPEND, s"(${o.possibleStrings}|${n.possibleStrings})" From 8cc8a18c16e68719ff18d24310714ae7d38f150a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 14:16:51 +0100 Subject: [PATCH 068/316] 1) The string definition analysis can now handle multiple UVars 2) Within the test methods of the test suite, it is now possible to have multiple calls to analyzeString. --- .../string_definition/TestMethods.java | 201 ++++++++---------- .../string_definition/StringDefinitions.java | 8 +- .../fpcf/LocalStringDefinitionTest.scala | 42 ++-- .../LocalStringDefinitionMatcher.scala | 74 ++++--- .../LocalStringDefinitionAnalysis.scala | 63 +++--- .../InterpretationHandler.scala | 19 +- .../analyses/string_definition/package.scala | 2 +- .../properties/StringConstancyProperty.scala | 12 +- .../StringConstancyInformation.scala | 27 +++ .../properties/StringTree.scala | 11 +- 10 files changed, 232 insertions(+), 227 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 15d188faa2..d4177457ee 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -9,6 +9,9 @@ import java.nio.file.Paths; import java.util.Random; +import static org.opalj.fpcf.properties.string_definition.StringConstancyLevel.CONSTANT; +import static org.opalj.fpcf.properties.string_definition.StringConstancyLevel.PARTIALLY_CONSTANT; + /** * This file contains various tests for the StringDefinitionAnalysis. The following things are to be * considered when adding test cases: @@ -53,29 +56,22 @@ public class TestMethods { public void analyzeString(String s) { } - @StringDefinitions( - value = "read-only string, trivial case", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "java.lang.String" - ) - public void constantString() { - analyzeString("java.lang.String"); - } - @StringDefinitions( value = "read-only string variable, trivial case", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "java.lang.String" + expectedLevels = { CONSTANT, CONSTANT }, + expectedStrings = { "java.lang.String", "java.lang.String" } ) public void constantStringVariable() { + analyzeString("java.lang.String"); + String className = "java.lang.String"; analyzeString(className); } @StringDefinitions( value = "checks if a string value with one append is determined correctly", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "java.lang.string" + expectedLevels = { CONSTANT }, + expectedStrings = { "java.lang.string" } ) public void simpleStringConcat() { String className = "java.lang."; @@ -86,8 +82,8 @@ public void simpleStringConcat() { @StringDefinitions( value = "checks if a string value with > 1 appends is determined correctly", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "java.lang.string" + expectedLevels = { CONSTANT }, + expectedStrings = { "java.lang.string" } ) public void advStringConcat() { String className = "java."; @@ -100,8 +96,8 @@ public void advStringConcat() { @StringDefinitions( value = "checks if a string value with > 2 continuous appends is determined correctly", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "java.lang.String" + expectedLevels = { CONSTANT }, + expectedStrings = { "java.lang.String" } ) public void directAppendConcats() { StringBuilder sb = new StringBuilder("java"); @@ -111,8 +107,8 @@ public void directAppendConcats() { @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "\\w" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "\\w" } ) public void fromFunctionCall() { String className = getStringBuilderClassName(); @@ -121,8 +117,8 @@ public void fromFunctionCall() { @StringDefinitions( value = "constant string + string from function call => PARTIALLY_CONSTANT", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "java.lang.\\w" + expectedLevels = { PARTIALLY_CONSTANT }, + expectedStrings = { "java.lang.\\w" } ) public void fromConstantAndFunctionCall() { String className = "java.lang."; @@ -133,9 +129,9 @@ public void fromConstantAndFunctionCall() { @StringDefinitions( value = "array access with unknown index", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(java.lang.String|java.lang.StringBuilder|" - + "java.lang.System|java.lang.Runnable)" + expectedLevels = { CONSTANT }, + expectedStrings = { "(java.lang.String|java.lang.StringBuilder|" + + "java.lang.System|java.lang.Runnable)" } ) public void fromStringArray(int index) { String[] classes = { @@ -149,8 +145,8 @@ public void fromStringArray(int index) { @StringDefinitions( value = "a simple case where multiple definition sites have to be considered", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(java.lang.System|java.lang.Runtime)" + expectedLevels = { CONSTANT }, + expectedStrings = { "(java.lang.System|java.lang.Runtime)" } ) public void multipleConstantDefSites(boolean cond) { String s; @@ -165,8 +161,8 @@ public void multipleConstantDefSites(boolean cond) { @StringDefinitions( value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "((java.lang.Object|\\w)|java.lang.System|java.lang.\\w|\\w)" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "(java.lang.Object|\\w|java.lang.System|java.lang.\\w|\\w)" } ) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -194,8 +190,8 @@ public void multipleDefSites(int value) { @StringDefinitions( value = "if-else control structure which append to a string builder with an int expr", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "(x|[AnIntegerValue])" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "(x|[AnIntegerValue])" } ) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb = new StringBuilder(); @@ -210,8 +206,8 @@ public void ifElseWithStringBuilderWithIntExpr() { @StringDefinitions( value = "if-else control structure which append to a string builder with an int", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "([AnIntegerValue]|x)" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "([AnIntegerValue]|x)" } ) public void ifElseWithStringBuilderWithConstantInt() { StringBuilder sb = new StringBuilder(); @@ -226,8 +222,8 @@ public void ifElseWithStringBuilderWithConstantInt() { @StringDefinitions( value = "if-else control structure which append to a string builder", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(a|b)" + expectedLevels = { CONSTANT }, + expectedStrings = { "(a|b)" } ) public void ifElseWithStringBuilder1() { StringBuilder sb; @@ -242,8 +238,8 @@ public void ifElseWithStringBuilder1() { @StringDefinitions( value = "if-else control structure which append to a string builder", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b|c)" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b|c)" } ) public void ifElseWithStringBuilder2() { StringBuilder sb = new StringBuilder("a"); @@ -258,8 +254,8 @@ public void ifElseWithStringBuilder2() { @StringDefinitions( value = "if-else control structure which append to a string builder multiple times", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(bcd|xyz)" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(bcd|xyz)" } ) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); @@ -278,9 +274,9 @@ public void ifElseWithStringBuilder3() { @StringDefinitions( value = "simple for loop with known bounds", - expectedLevel = StringConstancyLevel.CONSTANT, + expectedLevels = { CONSTANT }, // Currently, the analysis does not support determining loop ranges => a(b)* - expectedStrings = "a(b)*" + expectedStrings = { "a(b)*" } ) public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); @@ -292,8 +288,8 @@ public void simpleForLoopWithKnownBounds() { @StringDefinitions( value = "simple for loop with unknown bounds", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)*" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b)*" } ) public void simpleForLoopWithUnknownBounds() { int limit = new Random().nextInt(); @@ -306,8 +302,8 @@ public void simpleForLoopWithUnknownBounds() { @StringDefinitions( value = "if-else control structure within a for loop with known loop bounds", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "((x|[AnIntegerValue]))*" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "((x|[AnIntegerValue]))*" } ) public void ifElseInLoopWithKnownBounds() { StringBuilder sb = new StringBuilder(); @@ -324,8 +320,8 @@ public void ifElseInLoopWithKnownBounds() { @StringDefinitions( value = "if-else control structure within a for loop and with an append afterwards", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "((x|[AnIntegerValue]))*yz" + expectedLevels = { PARTIALLY_CONSTANT }, + expectedStrings = { "((x|[AnIntegerValue]))*yz" } ) public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); @@ -343,8 +339,8 @@ public void ifElseInLoopWithAppendAfterwards() { @StringDefinitions( value = "if control structure without an else", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)?" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b)?" } ) public void ifWithoutElse() { StringBuilder sb = new StringBuilder("a"); @@ -357,8 +353,8 @@ public void ifWithoutElse() { @StringDefinitions( value = "a case where multiple optional definition sites have to be considered.", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b|c)?" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b|c)?" } ) public void multipleOptionalAppendSites(int value) { StringBuilder sb = new StringBuilder("a"); @@ -377,43 +373,11 @@ public void multipleOptionalAppendSites(int value) { analyzeString(sb.toString()); } - @StringDefinitions( - value = "an extensive example with many control structures where appends follow " - + "after the read", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(iv1|iv2): " - ) - public void extensiveEarlyRead(boolean cond) { - StringBuilder sb = new StringBuilder(); - if (cond) { - sb.append("iv1"); - } else { - sb.append("iv2"); - } - System.out.println(sb); - sb.append(": "); - - analyzeString(sb.toString()); - - Random random = new Random(); - while (random.nextFloat() > 5.) { - if (random.nextInt() % 2 == 0) { - sb.append("great!"); - } - } - - if (sb.indexOf("great!") > -1) { - sb.append(getRuntimeClassName()); - } - - // TODO: Noch ein Aufruf zu analyzeString - } - @StringDefinitions( value = "case with a nested loop where in the outer loop a StringBuilder is created " + "that is later read", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)*" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b)*" } ) public void nestedLoops(int range) { for (int i = 0; i < range; i++) { @@ -427,8 +391,8 @@ public void nestedLoops(int range) { @StringDefinitions( value = "some example that makes use of a StringBuffer instead of a StringBuilder", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "((x|[AnIntegerValue]))*yz" + expectedLevels = { PARTIALLY_CONSTANT }, + expectedStrings = { "((x|[AnIntegerValue]))*yz" } ) public void stringBufferExample() { StringBuffer sb = new StringBuffer(); @@ -446,8 +410,8 @@ public void stringBufferExample() { @StringDefinitions( value = "while-true example", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)*" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b)*" } ) public void whileWithBreak() { StringBuilder sb = new StringBuilder("a"); @@ -462,8 +426,8 @@ public void whileWithBreak() { @StringDefinitions( value = "an example with a non-while-true loop containing a break", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "a(b)*" + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b)*" } ) public void whileWithBreak(int i) { StringBuilder sb = new StringBuilder("a"); @@ -480,8 +444,8 @@ public void whileWithBreak(int i) { @StringDefinitions( value = "an extensive example with many control structures", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + expectedLevels = { CONSTANT, PARTIALLY_CONSTANT }, + expectedStrings = { "(iv1|iv2): ", "(iv1|iv2): (great!)*(\\w)?" } ) public void extensive(boolean cond) { StringBuilder sb = new StringBuilder(); @@ -493,6 +457,8 @@ public void extensive(boolean cond) { System.out.println(sb); sb.append(": "); + analyzeString(sb.toString()); + Random random = new Random(); while (random.nextFloat() > 5.) { if (random.nextInt() % 2 == 0) { @@ -509,8 +475,8 @@ public void extensive(boolean cond) { @StringDefinitions( value = "an example with a throw (and no try-catch-finally)", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "File Content:\\w" + expectedLevels = { PARTIALLY_CONSTANT }, + expectedStrings = { "File Content:\\w" } ) public void withThrow(String filename) throws IOException { StringBuilder sb = new StringBuilder("File Content:"); @@ -521,8 +487,15 @@ public void withThrow(String filename) throws IOException { @StringDefinitions( value = "case with a try-finally exception", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "File Content:(\\w)?" + // Currently, multiple expectedLevels and expectedStrings values are necessary because + // the three-address code contains multiple calls to 'analyzeString' which are currently + // not filtered out + expectedLevels = { + PARTIALLY_CONSTANT, PARTIALLY_CONSTANT, PARTIALLY_CONSTANT + }, + expectedStrings = { + "File Content:(\\w)?", "File Content:(\\w)?", "File Content:(\\w)?" + } ) public void withException(String filename) { StringBuilder sb = new StringBuilder("File Content:"); @@ -537,8 +510,12 @@ public void withException(String filename) { @StringDefinitions( value = "case with a try-catch-finally exception", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "=====(\\w|=====)" + expectedLevels = { + PARTIALLY_CONSTANT, PARTIALLY_CONSTANT, PARTIALLY_CONSTANT + }, + expectedStrings = { + "=====(\\w|=====)", "=====(\\w|=====)", "=====(\\w|=====)" + } ) public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); @@ -554,8 +531,8 @@ public void tryCatchFinally(String filename) { @StringDefinitions( value = "a simple example with a StringBuilder#setLength to clear it", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "\\w" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "\\w" } ) public void simpleClearWithSetLengthExample(int value) { StringBuilder sb = new StringBuilder("init_value:"); @@ -566,8 +543,8 @@ public void simpleClearWithSetLengthExample(int value) { @StringDefinitions( value = "a simple example with a StringBuilder#new to clear it", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "\\w" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "\\w" } ) public void simpleClearWithNewExample(int value) { StringBuilder sb = new StringBuilder("init_value:"); @@ -579,8 +556,8 @@ public void simpleClearWithNewExample(int value) { @StringDefinitions( value = "a more advanced example with a StringBuilder#setLength to clear it", - expectedLevel = StringConstancyLevel.CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" + expectedLevels = { CONSTANT }, + expectedStrings = { "(init_value:Hello, world!Goodbye|Goodbye)" } ) public void advancedClearExampleWithSetLength(int value) { StringBuilder sb = new StringBuilder("init_value:"); @@ -595,8 +572,8 @@ public void advancedClearExampleWithSetLength(int value) { @StringDefinitions( value = "a simple example with a StringBuilder#replace call", - expectedLevel = StringConstancyLevel.DYNAMIC, - expectedStrings = "\\w" + expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedStrings = { "\\w" } ) public void simpleReplaceExample() { StringBuilder sb = new StringBuilder("init_value"); @@ -606,8 +583,8 @@ public void simpleReplaceExample() { @StringDefinitions( value = "a more advanced example with a StringBuilder#replace call", - expectedLevel = StringConstancyLevel.PARTIALLY_CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|\\wGoodbye)" + expectedLevels = { PARTIALLY_CONSTANT }, + expectedStrings = { "(init_value:Hello, world!Goodbye|\\wGoodbye)" } ) public void advancedReplaceExample(int value) { StringBuilder sb = new StringBuilder("init_value:"); @@ -622,8 +599,8 @@ public void advancedReplaceExample(int value) { // @StringDefinitions( // value = "a case with a switch with missing breaks", - // expectedLevel = StringConstancyLevel.CONSTANT, - // expectedStrings = "a(bc|c)?" + // expectedLevels = {StringConstancyLevel.CONSTANT}, + // expectedStrings ={ "a(bc|c)?" } // ) // public void switchWithMissingBreak(int value) { // StringBuilder sb = new StringBuilder("a"); @@ -642,8 +619,8 @@ public void advancedReplaceExample(int value) { // // @StringDefinitions( // // value = "checks if a string value with > 2 continuous appends and a second " // // + "StringBuilder is determined correctly", - // // expectedLevel = StringConstancyLevel.CONSTANT, - // // expectedStrings = "java.langStringB." + // // expectedLevels = {StringConstancyLevel.CONSTANT}, + // // expectedStrings ={ "java.langStringB." } // // ) // // public void directAppendConcats2() { // // StringBuilder sb = new StringBuilder("java"); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index ffc86f4727..aac88b238e 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -29,10 +29,10 @@ String value() default "N/A"; /** - * This value determines the expectedLevel of freedom for a string field or local variable to be - * changed. The default value is {@link StringConstancyLevel#DYNAMIC}. + * This value determines the expected levels of freedom for a string field or local variable to + * be changed. */ - StringConstancyLevel expectedLevel() default StringConstancyLevel.DYNAMIC; + StringConstancyLevel[] expectedLevels(); /** * A regexp like string that describes the elements that are expected. For the rules, refer to @@ -40,6 +40,6 @@ * For example, "(* | (hello | world)^5)" describes a string which can 1) either be any string * or 2) a five time concatenation of "hello" and/or "world". */ - String expectedStrings() default ""; + String[] expectedStrings(); } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala index ee083d7361..8a06e1ba1f 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -7,15 +7,18 @@ import java.io.File import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method +import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.cg.V import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis import org.opalj.fpcf.analyses.string_definition.LocalStringDefinitionAnalysis import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall import scala.collection.mutable +import scala.collection.mutable.ListBuffer /** * Tests whether the StringTrackingAnalysis works correctly. @@ -39,27 +42,21 @@ class LocalStringDefinitionTest extends PropertiesTest { } /** - * Extracts a [[org.opalj.tac.UVar]] from a set of statements. The location of the UVar is - * identified the argument to the very first call to TestMethods#analyzeString. + * Extracts [[org.opalj.tac.UVar]]s from a set of statements. The locations of the UVars are + * identified by the argument to the very first call to TestMethods#analyzeString. * - * @param stmts The statements from which to extract the UVar, usually the method that contains - * the call to TestMethods#analyzeString. - * @return Returns the argument of the TestMethods#analyzeString as a DUVar. In case the - * expected analyze method is not present, None is returned. + * @param cfg The control flow graph from which to extract the UVar, usually derived from the + * method that contains the call(s) to TestMethods#analyzeString. + * @return Returns the arguments of the TestMethods#analyzeString as a DUVars list in the order + * in which they occurred in the given statements. */ - private def extractUVar(stmts: Array[Stmt[V]]): Option[V] = { - val relMethodCalls = stmts.filter { + private def extractUVars(cfg: CFG[Stmt[V], TACStmts[V]]): List[V] = { + cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ declClass.toJavaClass.getName == LocalStringDefinitionTest.fqTestMethodsClass && name == LocalStringDefinitionTest.nameTestMethod case _ ⇒ false - } - - if (relMethodCalls.isEmpty) { - return None - } - - Some(relMethodCalls.head.asVirtualMethodCall.params.head.asVar) + }.map(_.asVirtualMethodCall.params.head.asVar).toList } /** @@ -70,7 +67,6 @@ class LocalStringDefinitionTest extends PropertiesTest { * @return True if the `a` is of type StringDefinitions and false otherwise. */ private def isStringUsageAnnotation(a: Annotation): Boolean = - // TODO: Is there a better way than string comparison? a.annotationType.toJavaClass.getName == LocalStringDefinitionTest.fqStringDefAnnotation describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { @@ -84,18 +80,20 @@ class LocalStringDefinitionTest extends PropertiesTest { // We need a "method to entity" matching for the evaluation (see further below) val m2e = mutable.HashMap[Method, (Entity, Method)]() val tacProvider = p.get(DefaultTACAIKey) + p.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || isStringUsageAnnotation(a) ) } foreach { m ⇒ - extractUVar(tacProvider(m).stmts) match { - case Some(uvar) ⇒ - val e = Tuple2(uvar, m) - ps.force(e, StringConstancyProperty.key) - m2e += (m → e) - case _ ⇒ + extractUVars(tacProvider(m).cfg).foreach { uvar ⇒ + if (!m2e.contains(m)) { + m2e += (m → Tuple2(ListBuffer(uvar), m)) + } else { + m2e(m)._1.asInstanceOf[ListBuffer[V]].append(uvar) + } } + ps.force((m2e(m)._1.asInstanceOf[ListBuffer[V]].toList, m), StringConstancyProperty.key) } // As entity, we need not the method but a tuple (DUVar, Method), thus this transformation diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 8424a33ae6..36a31dc3c0 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -3,11 +3,16 @@ package org.opalj.fpcf.properties.string_definition import org.opalj.br.analyses.Project import org.opalj.br.AnnotationLike +import org.opalj.br.EnumValue import org.opalj.br.ObjectType +import org.opalj.br.StringValue +import org.opalj.collection.immutable.RefArray import org.opalj.fpcf.properties.AbstractPropertyMatcher import org.opalj.fpcf.Property import org.opalj.fpcf.properties.StringConstancyProperty +import scala.collection.mutable.ListBuffer + /** * Matches local variable's `StringConstancy` property. The match is successful if the * variable has a constancy level that matches its actual usage and the expected values are present. @@ -17,32 +22,28 @@ import org.opalj.fpcf.properties.StringConstancyProperty class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { /** - * @param a An annotation like of type - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. - * @return Returns the constancy level specified in the annotation as a string and `None` in - * case the element with the name 'expectedLevel' was not present in the annotation - * (should never be the case if an annotation of the correct type is passed). + * Returns the constancy levels specified in the annotation as a list of lower-cased strings. */ - private def getConstancyLevel(a: AnnotationLike): Option[String] = { - a.elementValuePairs.find(_.name == "expectedLevel") match { - case Some(el) ⇒ Some(el.value.asEnumValue.constName) - case None ⇒ None + private def getExpectedConstancyLevels(a: AnnotationLike): List[String] = + a.elementValuePairs.find(_.name == "expectedLevels") match { + case Some(el) ⇒ + el.value.asArrayValue.values.asInstanceOf[RefArray[EnumValue]].map { + ev: EnumValue ⇒ ev.constName.toLowerCase + }.toList + case None ⇒ List() } - } /** - * @param a An annotation like of type - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. - * @return Returns the ''expectedStrings'' value from the annotation or `None` in case the - * element with the name ''expectedStrings'' was not present in the annotation (should - * never be the case if an annotation of the correct type is passed). + * Returns the expected strings specified in the annotation as a list. */ - private def getExpectedStrings(a: AnnotationLike): Option[String] = { + private def getExpectedStrings(a: AnnotationLike): List[String] = a.elementValuePairs.find(_.name == "expectedStrings") match { - case Some(el) ⇒ Some(el.value.asStringValue.value) - case None ⇒ None + case Some(el) ⇒ + el.value.asArrayValue.values.asInstanceOf[RefArray[StringValue]].map { + sc: StringValue ⇒ sc.value + }.toList + case None ⇒ List() } - } /** * @inheritdoc @@ -54,17 +55,32 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { a: AnnotationLike, properties: Traversable[Property] ): Option[String] = { - val prop = properties.filter( - _.isInstanceOf[StringConstancyProperty] - ).head.asInstanceOf[StringConstancyProperty] - val reducedProp = prop.stringTree.simplify().groupRepetitionElements().reduce() + val actLevels = ListBuffer[String]() + val actStrings = ListBuffer[String]() + if (properties.nonEmpty) { + properties.head match { + case prop: StringConstancyProperty ⇒ + prop.stringConstancyInformation.foreach { nextSci ⇒ + actLevels.append(nextSci.constancyLevel.toString.toLowerCase) + actStrings.append(nextSci.possibleStrings.toString) + } + case _ ⇒ + } + } + + val expLevels = getExpectedConstancyLevels(a) + val expStrings = getExpectedStrings(a) + val errorMsg = s"Levels: ${actLevels.mkString("{", ",", "}")}, "+ + s"Strings: ${actStrings.mkString("{", ",", "}")}" - val expLevel = getConstancyLevel(a).get - val actLevel = reducedProp.constancyLevel.toString - val expStrings = getExpectedStrings(a).get - val actStrings = reducedProp.possibleStrings - if ((expLevel.toLowerCase != actLevel.toLowerCase) || (expStrings != actStrings)) { - return Some(reducedProp.toString) + // The lists need to have the same sizes and need to match element-wise + if (actLevels.size != expLevels.size || actStrings.size != expStrings.size) { + return Some(errorMsg) + } + for (i ← actLevels.indices) { + if (expLevels(i) != actLevels(i) || expStrings(i) != actStrings(i)) { + return Some(errorMsg) + } } None diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 0db00fb636..a1faf79c27 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -9,15 +9,17 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.ComputationSpecification -import org.opalj.fpcf.NoResult import org.opalj.fpcf.analyses.string_definition.preprocessing.AbstractPathFinder import org.opalj.fpcf.analyses.string_definition.preprocessing.DefaultPathFinder import org.opalj.fpcf.analyses.string_definition.preprocessing.PathTransformer import org.opalj.fpcf.Result import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt +import scala.collection.mutable.ListBuffer + class StringTrackingAnalysisContext( val stmts: Array[Stmt[V]] ) @@ -41,49 +43,40 @@ class LocalStringDefinitionAnalysis( ) extends FPCFAnalysis { def analyze(data: P): PropertyComputationResult = { + val scis = ListBuffer[StringConstancyInformation]() + val tacProvider = p.get(SimpleTACAIKey) val stmts = tacProvider(data._2).stmts val cfg = tacProvider(data._2).cfg - val defSites = data._1.definedBy.toArray.sorted - val expr = stmts(defSites.head).asAssignment.expr - val pathFinder: AbstractPathFinder = new DefaultPathFinder() - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - val initDefSites = InterpretationHandler.findDefSiteOfInit( - expr.asVirtualFunctionCall, stmts - ) - if (initDefSites.isEmpty) { - throw new IllegalStateException("did not find any initializations!") - } - - val paths = pathFinder.findPaths(initDefSites, cfg) - val leanPaths = paths.makeLeanPath(data._1, stmts) - // The following case should only occur if an object is queried that does not occur at - // all within the CFG - if (leanPaths.isEmpty) { - return NoResult - } + data._1.foreach { nextUVar ⇒ + val defSites = nextUVar.definedBy.toArray.sorted + val expr = stmts(defSites.head).asAssignment.expr + val pathFinder: AbstractPathFinder = new DefaultPathFinder() + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + val initDefSites = InterpretationHandler.findDefSiteOfInit( + expr.asVirtualFunctionCall, stmts + ) + if (initDefSites.isEmpty) { + throw new IllegalStateException("did not find any initializations!") + } - val tree = new PathTransformer(cfg).pathToStringTree(leanPaths.get) - if (tree.isDefined) { - Result(data, StringConstancyProperty(tree.get)) - } else { - NoResult - } - } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings - else { - val paths = pathFinder.findPaths(defSites.toList, cfg) - if (paths.elements.isEmpty) { - NoResult - } else { - val tree = new PathTransformer(cfg).pathToStringTree(paths) + val paths = pathFinder.findPaths(initDefSites, cfg) + val leanPaths = paths.makeLeanPath(nextUVar, stmts) + val tree = new PathTransformer(cfg).pathToStringTree(leanPaths.get) if (tree.isDefined) { - Result(data, StringConstancyProperty(tree.get)) - } else { - NoResult + scis.append(tree.get.simplify().groupRepetitionElements().reduce()) } + } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings + else { + val interHandler = InterpretationHandler(cfg) + scis.append(StringConstancyInformation.reduceMultiple( + nextUVar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList + )) } } + + Result(data, StringConstancyProperty(scis.toList)) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 2d30014f94..4705c9591a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -88,10 +88,10 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { } /** - * This function serves as a wrapper function for [[InterpretationHandler.processDefSite]] in the - * sense that it processes multiple definition sites. Thus, it may throw an exception as well if - * an expression referenced by a definition site cannot be processed. The same rules as for - * [[InterpretationHandler.processDefSite]] apply. + * This function serves as a wrapper function for [[InterpretationHandler.processDefSite]] in + * the sense that it processes multiple definition sites. Thus, it may throw an exception as + * well if an expression referenced by a definition site cannot be processed. The same rules as + * for [[InterpretationHandler.processDefSite]] apply. * * @param defSites The definition sites to process. * @return Returns a list of lists of [[StringConstancyInformation]]. Note that this function @@ -107,10 +107,10 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { } /** - * The [[InterpretationHandler]] keeps an internal state for correct and faster processing. As long as a - * single object within a CFG is analyzed, there is no need to reset the state. However, when - * analyzing a second object (even the same object) it is necessary to call `reset` to reset the - * internal state. Otherwise, incorrect results will be produced. + * The [[InterpretationHandler]] keeps an internal state for correct and faster processing. As + * long as a single object within a CFG is analyzed, there is no need to reset the state. + * However, when analyzing a second object (even the same object) it is necessary to call + * `reset` to reset the internal state. Otherwise, incorrect results will be produced. * (Alternatively, you could instantiate another [[InterpretationHandler]] instance.) */ def reset(): Unit = { @@ -124,7 +124,8 @@ object InterpretationHandler { /** * @see [[InterpretationHandler]] */ - def apply(cfg: CFG[Stmt[V], TACStmts[V]]): InterpretationHandler = new InterpretationHandler(cfg) + def apply(cfg: CFG[Stmt[V], TACStmts[V]]): InterpretationHandler = + new InterpretationHandler(cfg) /** * Checks whether an expression contains a call to [[StringBuilder#toString]] or diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala index 9c503d9b51..6ed3282711 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala @@ -21,6 +21,6 @@ package object string_definition { * [[LocalStringDefinitionAnalysis]] processes a local variable within the context of a * particular context, i.e., the method in which it is declared and used. */ - type P = (V, Method) + type P = (List[V], Method) } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index 2c0a2f7b26..fb1d04db0b 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -11,21 +11,19 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.fpcf.string_definition.properties.StringConstancyType -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConst sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformation { type Self = StringConstancyProperty } class StringConstancyProperty( - val stringTree: StringTree + val stringConstancyInformation: List[StringConstancyInformation] ) extends Property with StringConstancyPropertyMetaInformation { final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - stringTree.reduce().toString + stringConstancyInformation.toString } } @@ -39,7 +37,7 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { PropertyKeyName, (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - StringConstancyProperty(StringTreeConst(StringConstancyInformation( + StringConstancyProperty(List(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, StringConstancyInformation.UnknownWordSymbol @@ -51,7 +49,7 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { } def apply( - stringTree: StringTree - ): StringConstancyProperty = new StringConstancyProperty(stringTree) + stringConstancyInformation: List[StringConstancyInformation] + ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 1eabab8299..5159727a23 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -38,4 +38,31 @@ object StringConstancyInformation { */ val InfiniteRepetitionSymbol: String = "*" + /** + * Takes a list of [[StringConstancyInformation]] and reduces them to a single one by or-ing + * them together (the level is determined by finding the most general level; the type is set to + * [[StringConstancyType.APPEND]] and the possible strings are concatenated using a pipe and + * then enclosed by brackets. + * + * @param scis The information to reduce. If a list with one element is passed, this element is + * returned (without being modified in any way); a list with > 1 element is reduced + * as described above; the empty list will throw an error! + * @return Returns the reduced information in the fashion described above. + */ + def reduceMultiple(scis: List[StringConstancyInformation]): StringConstancyInformation = { + scis.length match { + case 1 ⇒ scis.head + case _ ⇒ // Reduce + val reduced = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, + s"${o.possibleStrings}|${n.possibleStrings}" + )) + // Modify possibleStrings value + StringConstancyInformation( + reduced.constancyLevel, reduced.constancyType, s"(${reduced.possibleStrings})" + ) + } + } + } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 21bde1973a..5d86666d26 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -272,14 +272,9 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * @return A [[StringConstancyInformation]] instance that flatly describes this tree. */ def reduce(): StringConstancyInformation = { - // The reduceLeft is necessary as reduceAcc might return a list, e.g., a clear occurred. In - // such cases, concatenate the values by or-ing them. - val reduced = reduceAcc(this) - reduced.reduceLeft((o, n) ⇒ StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - StringConstancyType.APPEND, - s"(${o.possibleStrings}|${n.possibleStrings})" - )) + // The call to reduceMultiple is necessary as reduceAcc might return a list, e.g., if a + // clear occurred + StringConstancyInformation.reduceMultiple(reduceAcc(this)) } /** From ad37be0be24e73d122f43a693397bc0f922efcae Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 14:23:15 +0100 Subject: [PATCH 069/316] Made the 'pathToStringTree' function less defensive. --- .../LocalStringDefinitionAnalysis.scala | 4 +--- .../preprocessing/PathTransformer.scala | 19 +++++++------------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index a1faf79c27..99fdd4680c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -64,9 +64,7 @@ class LocalStringDefinitionAnalysis( val paths = pathFinder.findPaths(initDefSites, cfg) val leanPaths = paths.makeLeanPath(nextUVar, stmts) val tree = new PathTransformer(cfg).pathToStringTree(leanPaths.get) - if (tree.isDefined) { - scis.append(tree.get.simplify().groupRepetitionElements().reduce()) - } + scis.append(tree.simplify().groupRepetitionElements().reduce()) } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { val interHandler = InterpretationHandler(cfg) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index c70e7c3b6d..05239e13c0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -56,11 +56,7 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { val processedSubPath = pathToStringTree( Path(npe.element.toList), resetExprHandler = false ) - if (processedSubPath.isDefined) { - Some(StringTreeRepetition(processedSubPath.get)) - } else { - None - } + Some(StringTreeRepetition(processedSubPath)) case _ ⇒ val processedSubPaths = npe.element.map( pathToTreeAcc @@ -115,18 +111,17 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * i.e., if `path` contains sites that could not be processed (successfully), they will * not occur in the tree. */ - def pathToStringTree(path: Path, resetExprHandler: Boolean = true): Option[StringTree] = { + def pathToStringTree(path: Path, resetExprHandler: Boolean = true): StringTree = { val tree = path.elements.size match { - case 0 ⇒ None - case 1 ⇒ pathToTreeAcc(path.elements.head) + case 1 ⇒ pathToTreeAcc(path.elements.head).get case _ ⇒ - val concatElement = Some(StringTreeConcat( + val concatElement = StringTreeConcat( path.elements.map(pathToTreeAcc).filter(_.isDefined).map(_.get).to[ListBuffer] - )) + ) // It might be that concat has only one child (because some interpreters might have // returned an empty list => In case of one child, return only that one - if (concatElement.isDefined && concatElement.get.children.size == 1) { - Some(concatElement.get.children.head) + if (concatElement.children.size == 1) { + concatElement.children.head } else { concatElement } From 49e2a104449fa81e4a35e4efd33c2f599f19a645 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 16:12:12 +0100 Subject: [PATCH 070/316] Reducing a StringTree now allows to pre-process the tree. --- .../string_definition/LocalStringDefinitionAnalysis.scala | 2 +- .../fpcf/string_definition/properties/StringTree.scala | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 99fdd4680c..694d2177f5 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -64,7 +64,7 @@ class LocalStringDefinitionAnalysis( val paths = pathFinder.findPaths(initDefSites, cfg) val leanPaths = paths.makeLeanPath(nextUVar, stmts) val tree = new PathTransformer(cfg).pathToStringTree(leanPaths.get) - scis.append(tree.simplify().groupRepetitionElements().reduce()) + scis.append(tree.reduce(true)) } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { val interHandler = InterpretationHandler(cfg) diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala index 5d86666d26..8d9b854b26 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala @@ -269,9 +269,15 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * Reduces this [[StringTree]] instance to a [[StringConstancyInformation]] object that captures * the information stored in this tree. * + * @param preprocess If set to true, `this` tree will be preprocess, i.e., it will be + * simplified and repetition elements be grouped. Note that preprocessing + * changes `this` instance! * @return A [[StringConstancyInformation]] instance that flatly describes this tree. */ - def reduce(): StringConstancyInformation = { + def reduce(preprocess: Boolean = false): StringConstancyInformation = { + if (preprocess) { + simplify().groupRepetitionElements() + } // The call to reduceMultiple is necessary as reduceAcc might return a list, e.g., if a // clear occurred StringConstancyInformation.reduceMultiple(reduceAcc(this)) From 46c93d6fc172385157ff9667c89dff1dfeef76bc Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 5 Dec 2018 16:21:51 +0100 Subject: [PATCH 071/316] Defined and toString method of StringConstancyProperty and made sure it gets printed when a test fails. --- .../string_definition/LocalStringDefinitionMatcher.scala | 4 ++-- .../org/opalj/fpcf/properties/StringConstancyProperty.scala | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 36a31dc3c0..26b5f07be9 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -70,8 +70,8 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { val expLevels = getExpectedConstancyLevels(a) val expStrings = getExpectedStrings(a) - val errorMsg = s"Levels: ${actLevels.mkString("{", ",", "}")}, "+ - s"Strings: ${actStrings.mkString("{", ",", "}")}" + val errorMsg = s"Levels: ${expLevels.mkString("{", ",", "}")}, "+ + s"Strings: ${expStrings.mkString("{", ",", "}")}" // The lists need to have the same sizes and need to match element-wise if (actLevels.size != expLevels.size || actStrings.size != expStrings.size) { diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index fb1d04db0b..7cb1afec66 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -23,7 +23,11 @@ class StringConstancyProperty( final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - stringConstancyInformation.toString + val levels = stringConstancyInformation.map( + _.constancyLevel.toString.toLowerCase + ).mkString("{", ",", "}") + val strings = stringConstancyInformation.map(_.possibleStrings).mkString("{", ",", "}") + s"Levels: $levels, Strings: $strings" } } From 355dc342fb8a5601d6ab9a82772a8af72fb61110 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Dec 2018 16:34:40 +0100 Subject: [PATCH 072/316] Collapsed a couple of test methods. --- .../string_definition/TestMethods.java | 241 +++++++----------- 1 file changed, 86 insertions(+), 155 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index d4177457ee..7dc53605f5 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -1,7 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string_definition; -import org.opalj.fpcf.properties.string_definition.StringConstancyLevel; import org.opalj.fpcf.properties.string_definition.StringDefinitions; import java.io.IOException; @@ -9,8 +8,7 @@ import java.nio.file.Paths; import java.util.Random; -import static org.opalj.fpcf.properties.string_definition.StringConstancyLevel.CONSTANT; -import static org.opalj.fpcf.properties.string_definition.StringConstancyLevel.PARTIALLY_CONSTANT; +import static org.opalj.fpcf.properties.string_definition.StringConstancyLevel.*; /** * This file contains various tests for the StringDefinitionAnalysis. The following things are to be @@ -61,7 +59,7 @@ public void analyzeString(String s) { expectedLevels = { CONSTANT, CONSTANT }, expectedStrings = { "java.lang.String", "java.lang.String" } ) - public void constantStringVariable() { + public void constantStringReads() { analyzeString("java.lang.String"); String className = "java.lang.String"; @@ -69,29 +67,22 @@ public void constantStringVariable() { } @StringDefinitions( - value = "checks if a string value with one append is determined correctly", - expectedLevels = { CONSTANT }, - expectedStrings = { "java.lang.string" } + value = "checks if a string value with append(s) is determined correctly", + expectedLevels = { CONSTANT, CONSTANT }, + expectedStrings = { "java.lang.String", "java.lang.Object" } ) public void simpleStringConcat() { - String className = "java.lang."; - System.out.println(className); - className += "string"; - analyzeString(className); - } + String className1 = "java.lang."; + System.out.println(className1); + className1 += "String"; + analyzeString(className1); - @StringDefinitions( - value = "checks if a string value with > 1 appends is determined correctly", - expectedLevels = { CONSTANT }, - expectedStrings = { "java.lang.string" } - ) - public void advStringConcat() { - String className = "java."; - System.out.println(className); - className += "lang."; - System.out.println(className); - className += "string"; - analyzeString(className); + String className2 = "java."; + System.out.println(className2); + className2 += "lang."; + System.out.println(className2); + className2 += "Object"; + analyzeString(className2); } @StringDefinitions( @@ -107,7 +98,7 @@ public void directAppendConcats() { @StringDefinitions( value = "at this point, function call cannot be handled => DYNAMIC", - expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedLevels = { DYNAMIC }, expectedStrings = { "\\w" } ) public void fromFunctionCall() { @@ -161,7 +152,7 @@ public void multipleConstantDefSites(boolean cond) { @StringDefinitions( value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", - expectedLevels = { StringConstancyLevel.DYNAMIC }, + expectedLevels = { DYNAMIC }, expectedStrings = { "(java.lang.Object|\\w|java.lang.System|java.lang.\\w|\\w)" } ) public void multipleDefSites(int value) { @@ -189,67 +180,67 @@ public void multipleDefSites(int value) { } @StringDefinitions( - value = "if-else control structure which append to a string builder with an int expr", - expectedLevels = { StringConstancyLevel.DYNAMIC }, - expectedStrings = { "(x|[AnIntegerValue])" } + value = "a case where multiple optional definition sites have to be considered.", + expectedLevels = { CONSTANT }, + expectedStrings = { "a(b|c)?" } ) - public void ifElseWithStringBuilderWithIntExpr() { - StringBuilder sb = new StringBuilder(); - int i = new Random().nextInt(); - if (i % 2 == 0) { - sb.append("x"); - } else { - sb.append(i + 1); + public void multipleOptionalAppendSites(int value) { + StringBuilder sb = new StringBuilder("a"); + switch (value) { + case 0: + sb.append("b"); + break; + case 1: + sb.append("c"); + break; + case 3: + break; + case 4: + break; } analyzeString(sb.toString()); } @StringDefinitions( - value = "if-else control structure which append to a string builder with an int", - expectedLevels = { StringConstancyLevel.DYNAMIC }, - expectedStrings = { "([AnIntegerValue]|x)" } + value = "if-else control structure which append to a string builder with an int expr " + + "and an int", + expectedLevels = { DYNAMIC, DYNAMIC }, + expectedStrings = { "(x|[AnIntegerValue])", "([AnIntegerValue]|x)" } ) - public void ifElseWithStringBuilderWithConstantInt() { - StringBuilder sb = new StringBuilder(); + public void ifElseWithStringBuilderWithIntExpr() { + StringBuilder sb1 = new StringBuilder(); + StringBuilder sb2 = new StringBuilder(); int i = new Random().nextInt(); if (i % 2 == 0) { - sb.append(i); + sb1.append("x"); + sb2.append(i); } else { - sb.append("x"); + sb1.append(i + 1); + sb2.append("x"); } - analyzeString(sb.toString()); + analyzeString(sb1.toString()); + analyzeString(sb2.toString()); } @StringDefinitions( value = "if-else control structure which append to a string builder", - expectedLevels = { CONSTANT }, - expectedStrings = { "(a|b)" } + expectedLevels = { CONSTANT, CONSTANT }, + expectedStrings = { "(a|b)", "a(b|c)" } ) public void ifElseWithStringBuilder1() { - StringBuilder sb; - int i = new Random().nextInt(); - if (i % 2 == 0) { - sb = new StringBuilder("a"); - } else { - sb = new StringBuilder("b"); - } - analyzeString(sb.toString()); - } + StringBuilder sb1; + StringBuilder sb2 = new StringBuilder("a"); - @StringDefinitions( - value = "if-else control structure which append to a string builder", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b|c)" } - ) - public void ifElseWithStringBuilder2() { - StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); if (i % 2 == 0) { - sb.append("b"); + sb1 = new StringBuilder("a"); + sb2.append("b"); } else { - sb.append("c"); + sb1 = new StringBuilder("b"); + sb2.append("c"); } - analyzeString(sb.toString()); + analyzeString(sb1.toString()); + analyzeString(sb2.toString()); } @StringDefinitions( @@ -273,10 +264,10 @@ public void ifElseWithStringBuilder3() { } @StringDefinitions( - value = "simple for loop with known bounds", - expectedLevels = { CONSTANT }, + value = "simple for loops with known and unknown bounds", + expectedLevels = { CONSTANT, CONSTANT }, // Currently, the analysis does not support determining loop ranges => a(b)* - expectedStrings = { "a(b)*" } + expectedStrings = { "a(b)*", "a(b)*" } ) public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); @@ -284,40 +275,15 @@ public void simpleForLoopWithKnownBounds() { sb.append("b"); } analyzeString(sb.toString()); - } - @StringDefinitions( - value = "simple for loop with unknown bounds", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b)*" } - ) - public void simpleForLoopWithUnknownBounds() { int limit = new Random().nextInt(); - StringBuilder sb = new StringBuilder("a"); + sb = new StringBuilder("a"); for (int i = 0; i < limit; i++) { sb.append("b"); } analyzeString(sb.toString()); } - @StringDefinitions( - value = "if-else control structure within a for loop with known loop bounds", - expectedLevels = { StringConstancyLevel.DYNAMIC }, - expectedStrings = { "((x|[AnIntegerValue]))*" } - ) - public void ifElseInLoopWithKnownBounds() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 20; i++) { - if (i % 2 == 0) { - sb.append("x"); - } else { - sb.append(i + 1); - } - } - - analyzeString(sb.toString()); - } - @StringDefinitions( value = "if-else control structure within a for loop and with an append afterwards", expectedLevels = { PARTIALLY_CONSTANT }, @@ -351,28 +317,6 @@ public void ifWithoutElse() { analyzeString(sb.toString()); } - @StringDefinitions( - value = "a case where multiple optional definition sites have to be considered.", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b|c)?" } - ) - public void multipleOptionalAppendSites(int value) { - StringBuilder sb = new StringBuilder("a"); - switch (value) { - case 0: - sb.append("b"); - break; - case 1: - sb.append("c"); - break; - case 3: - break; - case 4: - break; - } - analyzeString(sb.toString()); - } - @StringDefinitions( value = "case with a nested loop where in the outer loop a StringBuilder is created " + "that is later read", @@ -530,28 +474,22 @@ public void tryCatchFinally(String filename) { } @StringDefinitions( - value = "a simple example with a StringBuilder#setLength to clear it", - expectedLevels = { StringConstancyLevel.DYNAMIC }, - expectedStrings = { "\\w" } + value = "simple examples to clear a StringBuilder", + expectedLevels = { DYNAMIC, DYNAMIC }, + expectedStrings = { "\\w", "\\w" } ) - public void simpleClearWithSetLengthExample(int value) { - StringBuilder sb = new StringBuilder("init_value:"); - sb.setLength(0); - sb.append(getStringBuilderClassName()); - analyzeString(sb.toString()); - } + public void simpleClearExamples() { + StringBuilder sb1 = new StringBuilder("init_value:"); + sb1.setLength(0); + sb1.append(getStringBuilderClassName()); - @StringDefinitions( - value = "a simple example with a StringBuilder#new to clear it", - expectedLevels = { StringConstancyLevel.DYNAMIC }, - expectedStrings = { "\\w" } - ) - public void simpleClearWithNewExample(int value) { - StringBuilder sb = new StringBuilder("init_value:"); - System.out.println(sb.toString()); - sb = new StringBuilder(); - sb.append(getStringBuilderClassName()); - analyzeString(sb.toString()); + StringBuilder sb2 = new StringBuilder("init_value:"); + System.out.println(sb2.toString()); + sb2 = new StringBuilder(); + sb2.append(getStringBuilderClassName()); + + analyzeString(sb1.toString()); + analyzeString(sb2.toString()); } @StringDefinitions( @@ -571,30 +509,23 @@ public void advancedClearExampleWithSetLength(int value) { } @StringDefinitions( - value = "a simple example with a StringBuilder#replace call", - expectedLevels = { StringConstancyLevel.DYNAMIC }, - expectedStrings = { "\\w" } + value = "a simple and a little more advanced example with a StringBuilder#replace call", + expectedLevels = { DYNAMIC, PARTIALLY_CONSTANT }, + expectedStrings = { "\\w", "(init_value:Hello, world!Goodbye|\\wGoodbye)" } ) - public void simpleReplaceExample() { - StringBuilder sb = new StringBuilder("init_value"); - sb.replace(0, 5, "replaced_value"); - analyzeString(sb.toString()); - } + public void replaceExamples(int value) { + StringBuilder sb1 = new StringBuilder("init_value"); + sb1.replace(0, 5, "replaced_value"); + analyzeString(sb1.toString()); - @StringDefinitions( - value = "a more advanced example with a StringBuilder#replace call", - expectedLevels = { PARTIALLY_CONSTANT }, - expectedStrings = { "(init_value:Hello, world!Goodbye|\\wGoodbye)" } - ) - public void advancedReplaceExample(int value) { - StringBuilder sb = new StringBuilder("init_value:"); + sb1 = new StringBuilder("init_value:"); if (value < 10) { - sb.replace(0, value, "..."); + sb1.replace(0, value, "..."); } else { - sb.append("Hello, world!"); + sb1.append("Hello, world!"); } - sb.append("Goodbye"); - analyzeString(sb.toString()); + sb1.append("Goodbye"); + analyzeString(sb1.toString()); } // @StringDefinitions( From 3952140b73bdfc09cc92586c6591f04931025421 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Dec 2018 16:56:02 +0100 Subject: [PATCH 073/316] Added a test method with 'breaks' and 'continues'. --- .../string_definition/TestMethods.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 7dc53605f5..f1c349bc3a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -528,6 +528,47 @@ public void replaceExamples(int value) { analyzeString(sb1.toString()); } + @StringDefinitions( + value = "loops that use breaks and continues (or both)", + expectedLevels = { CONSTANT, CONSTANT, DYNAMIC }, + expectedStrings = { "abc((d)?)*", "", "((\\w)?)*" } + ) + public void breakContinueExamples(int value) { + StringBuilder sb1 = new StringBuilder("abc"); + for (int i = 0; i < value; i++) { + if (i % 7 == 1) { + break; + } else if (i % 3 == 0) { + continue; + } else { + sb1.append("d"); + } + } + analyzeString(sb1.toString()); + + StringBuilder sb2 = new StringBuilder(""); + for (int i = 0; i < value; i++) { + if (i % 2 == 0) { + break; + } + sb2.append("some_value"); + } + analyzeString(sb2.toString()); + + StringBuilder sb3 = new StringBuilder(); + for (int i = 0; i < 10; i++) { + if (sb3.toString().equals("")) { + // The analysis currently does not detect, that this statement is executed at + // most / exactly once as it fully relies on the three-address code and does not + // infer any semantics of conditionals + sb3.append(getRuntimeClassName()); + } else { + continue; + } + } + analyzeString(sb3.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevels = {StringConstancyLevel.CONSTANT}, From 2a7839d3b8b20ee8b273cd20e1ab6b877bcd84ba Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 6 Dec 2018 17:09:49 +0100 Subject: [PATCH 074/316] Added an example where in the condition of an 'if', a string is appended to a StringBuilder. --- .../fixtures/string_definition/TestMethods.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index f1c349bc3a..7a534708b5 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -569,6 +569,20 @@ public void breakContinueExamples(int value) { analyzeString(sb3.toString()); } + @StringDefinitions( + value = "an example where in the condition of an 'if', a string is appended to a " + + "StringBuilder", + expectedLevels = { CONSTANT }, + expectedStrings = { "java.lang.Runtime" } + ) + public void ifConditionAppendsToString(String className) { + StringBuilder sb = new StringBuilder(); + if (sb.append("java.lang.Runtime").toString().equals(className)) { + System.out.println("Yep, got the correct class!"); + } + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevels = {StringConstancyLevel.CONSTANT}, From 4dca4811f327d762c3be454796ddd03b60b23f0d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 7 Dec 2018 13:50:15 +0100 Subject: [PATCH 075/316] Made the 'makeLeanPath' less defensive. --- .../LocalStringDefinitionAnalysis.scala | 2 +- .../string_definition/preprocessing/Path.scala | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 694d2177f5..16e939fdde 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -63,7 +63,7 @@ class LocalStringDefinitionAnalysis( val paths = pathFinder.findPaths(initDefSites, cfg) val leanPaths = paths.makeLeanPath(nextUVar, stmts) - val tree = new PathTransformer(cfg).pathToStringTree(leanPaths.get) + val tree = new PathTransformer(cfg).pathToStringTree(leanPaths) scis.append(tree.reduce(true)) } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 63cb778238..cd10dbeb98 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -145,14 +145,13 @@ case class Path(elements: List[SubPath]) { * @return Returns a lean path of `this` path. That means, `this` instance will be stripped to * contain only [[FlatPathElement]]s and [[NestedPathElement]]s that contain a * definition or usage of `obj`. This includes the removal of [[NestedPathElement]]s - * not containing `obj`. In case `this` path does not contain `obj` at all, `None` will - * be returned. + * not containing `obj`. * * @note This function does not change the underlying `this` instance. Furthermore, all relevant * elements for the lean path will be copied, i.e., `this` instance and the returned * instance do not share any references. */ - def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): Option[Path] = { + def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): Path = { // Transform the list into a map to have a constant access time val siteMap = Map(getAllDefAndUseSites(obj, stmts) map { s ⇒ (s, Unit) }: _*) val leanPath = ListBuffer[SubPath]() @@ -176,11 +175,7 @@ case class Path(elements: List[SubPath]) { } } - if (leanPath.isEmpty) { - None - } else { - Some(Path(leanPath.toList)) - } + Path(leanPath.toList) } } From 062da727711a42c7ad5d5b45881f66cc4f4667d1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 8 Dec 2018 21:27:33 +0100 Subject: [PATCH 076/316] Extended the LocalStringDefinitionAnalysis to support the analysis of another string variable in order to produce (more) correct results. Test methods were added as well. --- .../string_definition/TestMethods.java | 55 ++++-- .../LocalStringDefinitionAnalysis.scala | 162 +++++++++++++++++- .../InterpretationHandler.scala | 18 ++ .../VirtualFunctionCallInterpreter.scala | 41 +++-- .../preprocessing/PathTransformer.scala | 50 ++++-- .../properties/StringConstancyProperty.scala | 24 ++- 6 files changed, 288 insertions(+), 62 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 7a534708b5..33fcf90444 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -583,6 +583,44 @@ public void ifConditionAppendsToString(String className) { analyzeString(sb.toString()); } + @StringDefinitions( + value = "checks if a string value with > 2 continuous appends and a second " + + "StringBuilder is determined correctly", + expectedLevels = { CONSTANT }, + expectedStrings = { "java.langStringB." } + ) + public void directAppendConcatsWith2ndStringBuilder() { + StringBuilder sb = new StringBuilder("java"); + StringBuilder sb2 = new StringBuilder("B"); + sb.append(".").append("lang"); + sb2.append("."); + sb.append("String"); + sb.append(sb2.toString()); + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "checks if the case, where the value of a StringBuilder depends on the " + + "complex construction of a second StringBuilder is determined correctly.", + expectedLevels = { CONSTANT }, + expectedStrings = { "java.lang.(Object|Runtime)" } + ) + public void secondStringBuilderRead(String className) { + StringBuilder sbObj = new StringBuilder("Object"); + StringBuilder sbRun = new StringBuilder("Runtime"); + + StringBuilder sb1 = new StringBuilder(); + if (sb1.length() == 0) { + sb1.append(sbObj.toString()); + } else { + sb1.append(sbRun.toString()); + } + + StringBuilder sb2 = new StringBuilder("java.lang."); + sb2.append(sb1.toString()); + analyzeString(sb2.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevels = {StringConstancyLevel.CONSTANT}, @@ -600,24 +638,7 @@ public void ifConditionAppendsToString(String className) { // break; // } // analyzeString(sb.toString()); - // } - // // @StringDefinitions( - // // value = "checks if a string value with > 2 continuous appends and a second " - // // + "StringBuilder is determined correctly", - // // expectedLevels = {StringConstancyLevel.CONSTANT}, - // // expectedStrings ={ "java.langStringB." } - // // ) - // // public void directAppendConcats2() { - // // StringBuilder sb = new StringBuilder("java"); - // // StringBuilder sb2 = new StringBuilder("B"); - // // sb.append(".").append("lang"); - // // sb2.append("."); - // // sb.append("String"); - // // sb.append(sb2.toString()); - // // analyzeString(sb.toString()); - - // // } private String getRuntimeClassName() { return "java.lang.Runtime"; diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 16e939fdde..6fa4dc36ba 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -2,6 +2,7 @@ package org.opalj.fpcf.analyses.string_definition import org.opalj.br.analyses.SomeProject +import org.opalj.br.cfg.CFG import org.opalj.fpcf.FPCFAnalysis import org.opalj.fpcf.PropertyComputationResult import org.opalj.fpcf.PropertyKind @@ -14,10 +15,25 @@ import org.opalj.fpcf.analyses.string_definition.preprocessing.DefaultPathFinder import org.opalj.fpcf.analyses.string_definition.preprocessing.PathTransformer import org.opalj.fpcf.Result import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler +import org.opalj.fpcf.analyses.string_definition.preprocessing.FlatPathElement +import org.opalj.fpcf.analyses.string_definition.preprocessing.NestedPathElement import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt +import org.opalj.fpcf.analyses.string_definition.preprocessing.Path +import org.opalj.fpcf.analyses.string_definition.preprocessing.SubPath +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.IntermediateEP +import org.opalj.fpcf.IntermediateResult +import org.opalj.fpcf.NoResult +import org.opalj.fpcf.Property +import org.opalj.fpcf.SomeEPS +import org.opalj.tac.ExprStmt +import org.opalj.tac.TACStmts +import scala.collection.mutable import scala.collection.mutable.ListBuffer class StringTrackingAnalysisContext( @@ -42,13 +58,32 @@ class LocalStringDefinitionAnalysis( val project: SomeProject ) extends FPCFAnalysis { + /** + * This class is to be used to store state information that are required at a later point in + * time during the analysis, e.g., due to the fact that another analysis had to be triggered to + * have all required information ready for a final result. + */ + private case class ComputationState( + // The lean path that was computed + computedLeanPath: Path, + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + var2IndexMapping: mutable.LinkedHashMap[V, Int], + // The control flow graph on which the computedLeanPath is based + cfg: CFG[Stmt[V], TACStmts[V]] + ) + + private[this] val states = mutable.Map[P, ComputationState]() + def analyze(data: P): PropertyComputationResult = { + // scis stores the final StringConstancyInformation val scis = ListBuffer[StringConstancyInformation]() - val tacProvider = p.get(SimpleTACAIKey) val stmts = tacProvider(data._2).stmts val cfg = tacProvider(data._2).cfg + // If not empty, this routine can only produce an intermediate result + val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() + data._1.foreach { nextUVar ⇒ val defSites = nextUVar.definedBy.toArray.sorted val expr = stmts(defSites.head).asAssignment.expr @@ -57,14 +92,19 @@ class LocalStringDefinitionAnalysis( val initDefSites = InterpretationHandler.findDefSiteOfInit( expr.asVirtualFunctionCall, stmts ) - if (initDefSites.isEmpty) { - throw new IllegalStateException("did not find any initializations!") - } - val paths = pathFinder.findPaths(initDefSites, cfg) val leanPaths = paths.makeLeanPath(nextUVar, stmts) - val tree = new PathTransformer(cfg).pathToStringTree(leanPaths) - scis.append(tree.reduce(true)) + + // Find DUVars, that the analysis of the current entity depends on + val dependentVars = findDependentVars(leanPaths, stmts, data._1) + if (dependentVars.nonEmpty) { + val toAnalyze = (dependentVars.keys.toList, data._2) + val ep = propertyStore(toAnalyze, StringConstancyProperty.key) + dependees.put(toAnalyze, ep) + states.put(data, ComputationState(leanPaths, dependentVars, cfg)) + } else { + scis.append(new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true)) + } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { val interHandler = InterpretationHandler(cfg) @@ -74,7 +114,113 @@ class LocalStringDefinitionAnalysis( } } - Result(data, StringConstancyProperty(scis.toList)) + if (dependees.nonEmpty) { + IntermediateResult( + data, + StringConstancyProperty.upperBound, + StringConstancyProperty.lowerBound, + dependees.values, + continuation(data, dependees.values) + ) + } else { + Result(data, StringConstancyProperty(scis.toList)) + } + } + + /** + * Continuation function. + * + * @param data The data that was passed to the `analyze` function. + * @param dependees A list of dependencies that this analysis run depends on. + * @return This function can either produce a final result or another intermediate result. + */ + private def continuation( + data: P, dependees: Iterable[EOptionP[Entity, Property]] + )(eps: SomeEPS): PropertyComputationResult = { + val relevantState = states.get(data) + // For mapping the index of a FlatPathElement to StringConstancyInformation + val fpe2Sci = mutable.Map[Int, List[StringConstancyInformation]]() + eps match { + case FinalEP(e, p) ⇒ + val scis = p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + // Add mapping information + e.asInstanceOf[(List[V], _)]._1.asInstanceOf[List[V]].foreach { nextVar ⇒ + fpe2Sci.put(relevantState.get.var2IndexMapping(nextVar), scis) + } + // Compute final result + val sci = new PathTransformer(relevantState.get.cfg).pathToStringTree( + relevantState.get.computedLeanPath, fpe2Sci.toMap + ).reduce(true) + Result(data, StringConstancyProperty(List(sci))) + case IntermediateEP(_, lb, ub) ⇒ + IntermediateResult( + data, lb, ub, dependees, continuation(data, dependees) + ) + case _ ⇒ NoResult + } + + } + + /** + * Helper / accumulator function for finding dependees. For how dependees are detected, see + * [[findDependentVars]]. Returns a list of pairs of DUVar and the index of the + * [[FlatPathElement.element]] in which it occurs. + */ + private def findDependeesAcc( + subpath: SubPath, stmts: Array[Stmt[V]], foundDependees: ListBuffer[(V, Int)] + ): ListBuffer[(V, Int)] = { + subpath match { + case fpe: FlatPathElement ⇒ + // For FlatPathElements, search for DUVars on which the toString method is called + // and where these toString calls are the parameter of an append call + stmts(fpe.element) match { + case ExprStmt(_, outerExpr) ⇒ + if (InterpretationHandler.isStringBuilderBufferAppendCall(outerExpr)) { + val param = outerExpr.asVirtualFunctionCall.params.head.asVar + param.definedBy.foreach { ds ⇒ + val expr = stmts(ds).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + foundDependees.append(( + outerExpr.asVirtualFunctionCall.params.head.asVar, + fpe.element + )) + } + } + } + case _ ⇒ + } + foundDependees + case npe: NestedPathElement ⇒ + npe.element.foreach { nextSubpath ⇒ + findDependeesAcc(nextSubpath, stmts, foundDependees) + } + foundDependees + case _ ⇒ foundDependees + } + } + + /** + * Takes a path, this should be the lean path of a [[Path]], as well as a context in the form of + * statements, stmts, and detects all dependees within `path`. ''Dependees'' are found by + * looking at all elements in the path, and check whether the argument of an `append` call is a + * value that stems from a `toString` call of a [[StringBuilder]] or [[StringBuffer]]. This + * function then returns the found UVars along with the indices of those append statements. + * + * @note In order to make sure that a [[org.opalj.tac.DUVar]] does not depend on itself, pass a + * `ignore` list (elements in `ignore` will not be added to the dependees list). + */ + private def findDependentVars( + path: Path, stmts: Array[Stmt[V]], ignore: List[V] + ): mutable.LinkedHashMap[V, Int] = { + val dependees = mutable.LinkedHashMap[V, Int]() + path.elements.foreach { nextSubpath ⇒ + findDependeesAcc(nextSubpath, stmts, ListBuffer()).foreach { nextPair ⇒ + if (!ignore.contains(nextPair._1)) { + dependees.put(nextPair._1, nextPair._2) + } + } + } + dependees } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 4705c9591a..0dde5c6c5e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -144,6 +144,24 @@ object InterpretationHandler { case _ ⇒ false } + /** + * Checks whether an expression contains a call to [[StringBuilder#append]] or + * [[StringBuffer#append]]. + * + * @param expr The expression that is to be checked. + * @return Returns true if `expr` is a call to `append` of [[StringBuilder]] or + * [[StringBuffer]]. + */ + def isStringBuilderBufferAppendCall(expr: Expr[V]): Boolean = { + expr match { + case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ + val className = clazz.toJavaClass.getName + (className == "java.lang.StringBuilder" || className == "java.lang.StringBuffer") && + name == "append" + case _ ⇒ false + } + } + /** * Determines the definition site of the initialization of the base object that belongs to a * ''toString'' call. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 5bbe832858..8d7cf357fc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -47,7 +47,7 @@ class VirtualFunctionCallInterpreter( */ override def interpret(instr: T): List[StringConstancyInformation] = { instr.name match { - case "append" ⇒ interpretAppendCall(instr) + case "append" ⇒ interpretAppendCall(instr).getOrElse(List()) case "toString" ⇒ interpretToStringCall(instr) case "replace" ⇒ interpretReplaceCall(instr) case _ ⇒ List() @@ -61,24 +61,30 @@ class VirtualFunctionCallInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = { + ): Option[List[StringConstancyInformation]] = { val receiverValues = receiverValuesOfAppendCall(appendCall) val appendValue = valueOfAppendCall(appendCall) - // It might be that we have to go back as much as to a New expression. As they do not + // The case can occur that receiver and append value are empty; although, it is + // counter-intuitive, this case may occur if both, the receiver and the parameter, have been + // processed before + if (receiverValues.isEmpty && appendValue.isEmpty) { + None + } // It might be that we have to go back as much as to a New expression. As they do not // produce a result (= empty list), the if part - if (receiverValues.isEmpty) { - List(appendValue) - } else { - receiverValues.map { nextSci ⇒ + else if (receiverValues.isEmpty) { + Some(List(appendValue.get)) + } // Receiver and parameter information are available => Combine them + else { + Some(receiverValues.map { nextSci ⇒ StringConstancyInformation( StringConstancyLevel.determineForConcat( - nextSci.constancyLevel, appendValue.constancyLevel + nextSci.constancyLevel, appendValue.get.constancyLevel ), StringConstancyType.APPEND, - nextSci.possibleStrings + appendValue.possibleStrings + nextSci.possibleStrings + appendValue.get.possibleStrings ) - } + }) } } @@ -100,12 +106,12 @@ class VirtualFunctionCallInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V] - ): StringConstancyInformation = { + ): Option[StringConstancyInformation] = { // .head because we want to evaluate only the first argument of append val defSiteParamHead = call.params.head.asVar.definedBy.head var value = exprHandler.processDefSite(defSiteParamHead) // If defSiteParamHead points to a New, value will be the empty list. In that case, process - // the first use site (which is the call + // the first use site (which is the call) if (value.isEmpty) { value = exprHandler.processDefSite( cfg.code.instructions(defSiteParamHead).asAssignment.targetVar.usedBy.toArray.min @@ -114,21 +120,22 @@ class VirtualFunctionCallInterpreter( call.params.head.asVar.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ - InterpretationHandler.getStringConstancyInformationForInt + Some(InterpretationHandler.getStringConstancyInformationForInt) case ComputationalTypeFloat ⇒ - InterpretationHandler.getStringConstancyInformationForFloat + Some(InterpretationHandler.getStringConstancyInformationForFloat) // Otherwise, try to compute case _ ⇒ // It might be necessary to merge the values of the receiver and of the parameter value.size match { - case 1 ⇒ value.head - case _ ⇒ StringConstancyInformation( + case 0 ⇒ None + case 1 ⇒ Some(value.head) + case _ ⇒ Some(StringConstancyInformation( StringConstancyLevel.determineForConcat( value.head.constancyLevel, value(1).constancyLevel ), StringConstancyType.APPEND, value.head.possibleStrings + value(1).possibleStrings - ) + )) } } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 05239e13c0..64f1911909 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -4,6 +4,7 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringTree import org.opalj.fpcf.string_definition.properties.StringTreeConcat import org.opalj.fpcf.string_definition.properties.StringTreeCond @@ -33,10 +34,14 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { /** * Accumulator function for transforming a path into a StringTree element. */ - private def pathToTreeAcc(subpath: SubPath): Option[StringTree] = { + private def pathToTreeAcc( + subpath: SubPath, fpe2Sci: Map[Int, List[StringConstancyInformation]] + ): Option[StringTree] = { subpath match { case fpe: FlatPathElement ⇒ - val sciList = exprHandler.processDefSite(fpe.element) + val sciList = fpe2Sci.getOrElse( + fpe.element, exprHandler.processDefSite(fpe.element) + ) sciList.length match { case 0 ⇒ None case 1 ⇒ Some(StringTreeConst(sciList.head)) @@ -58,9 +63,9 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { ) Some(StringTreeRepetition(processedSubPath)) case _ ⇒ - val processedSubPaths = npe.element.map( - pathToTreeAcc - ).filter(_.isDefined).map(_.get) + val processedSubPaths = npe.element.map { ne ⇒ + pathToTreeAcc(ne, fpe2Sci) + }.filter(_.isDefined).map(_.get) if (processedSubPaths.nonEmpty) { npe.elementType.get match { case NestedPathType.CondWithAlternative ⇒ @@ -83,10 +88,11 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { } else { npe.element.size match { case 0 ⇒ None - case 1 ⇒ pathToTreeAcc(npe.element.head) + case 1 ⇒ pathToTreeAcc(npe.element.head, fpe2Sci) case _ ⇒ - val processed = - npe.element.map(pathToTreeAcc).filter(_.isDefined).map(_.get) + val processed = npe.element.map { ne ⇒ + pathToTreeAcc(ne, fpe2Sci) + }.filter(_.isDefined).map(_.get) if (processed.isEmpty) { None } else { @@ -102,21 +108,35 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * Takes a [[Path]] and transforms it into a [[StringTree]]. This implies an interpretation of * how to handle methods called on the object of interest (like `append`). * - * @param path The path element to be transformed. - * @param resetExprHandler Whether to reset the underlying [[InterpretationHandler]] or not. When calling - * this function from outside, the default value should do fine in most - * of the cases. For further information, see [[InterpretationHandler.reset]]. + * @param path The path element to be transformed. + * @param fpe2Sci A mapping from [[FlatPathElement.element]] values to + * [[StringConstancyInformation]]. Make use of this mapping if some + * StringConstancyInformation need to be used that the [[InterpretationHandler]] + * cannot infer / derive. For instance, if the exact value of an expression needs + * to be determined by calling the + * [[org.opalj.fpcf.analyses.string_definition.LocalStringDefinitionAnalysis]] on + * another instance, store this information in fpe2Sci. + * @param resetExprHandler Whether to reset the underlying [[InterpretationHandler]] or not. + * When calling this function from outside, the default value should do + * fine in most of the cases. For further information, see + * [[InterpretationHandler.reset]]. * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed * [[StringTree]] will be returned. Note that all elements of the tree will be defined, * i.e., if `path` contains sites that could not be processed (successfully), they will * not occur in the tree. */ - def pathToStringTree(path: Path, resetExprHandler: Boolean = true): StringTree = { + def pathToStringTree( + path: Path, + fpe2Sci: Map[Int, List[StringConstancyInformation]] = Map.empty, + resetExprHandler: Boolean = true + ): StringTree = { val tree = path.elements.size match { - case 1 ⇒ pathToTreeAcc(path.elements.head).get + case 1 ⇒ pathToTreeAcc(path.elements.head, fpe2Sci).get case _ ⇒ val concatElement = StringTreeConcat( - path.elements.map(pathToTreeAcc).filter(_.isDefined).map(_.get).to[ListBuffer] + path.elements.map { ne ⇒ + pathToTreeAcc(ne, fpe2Sci) + }.filter(_.isDefined).map(_.get).to[ListBuffer] ) // It might be that concat has only one child (because some interpreters might have // returned an empty list => In case of one child, return only that one diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index 7cb1afec66..f7091e8e62 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -41,11 +41,7 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { PropertyKeyName, (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - StringConstancyProperty(List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - ))) + lowerBound }, (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, (_: PropertyStore, _: Entity) ⇒ None @@ -56,4 +52,22 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { stringConstancyInformation: List[StringConstancyInformation] ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) + /** + * @return Returns the upper bound from a lattice-point of view. + */ + def upperBound: StringConstancyProperty = + StringConstancyProperty(List(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.APPEND + ))) + + /** + * @return Returns the lower bound from a lattice-point of view. + */ + def lowerBound: StringConstancyProperty = + StringConstancyProperty(List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + ))) + } From b8022561edd6c7653d4dccf9e5a4dfbdeaeb9bd9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 9 Dec 2018 10:49:57 +0100 Subject: [PATCH 077/316] Added a comment. --- .../LocalStringDefinitionAnalysis.scala | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 6fa4dc36ba..9a37680c01 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -37,7 +37,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer class StringTrackingAnalysisContext( - val stmts: Array[Stmt[V]] + val stmts: Array[Stmt[V]] ) /** @@ -64,14 +64,20 @@ class LocalStringDefinitionAnalysis( * have all required information ready for a final result. */ private case class ComputationState( - // The lean path that was computed - computedLeanPath: Path, - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - var2IndexMapping: mutable.LinkedHashMap[V, Int], - // The control flow graph on which the computedLeanPath is based - cfg: CFG[Stmt[V], TACStmts[V]] + // The lean path that was computed + computedLeanPath: Path, + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + var2IndexMapping: mutable.LinkedHashMap[V, Int], + // The control flow graph on which the computedLeanPath is based + cfg: CFG[Stmt[V], TACStmts[V]] ) + /** + * As executions of this analysis can be nested (since it may start itself), there might be + * several states to capture. In order to do so and enable each analysis instance to access its + * information, a map is used where the keys are the values fed into the analysis (which + * uniquely identify an analysis run) and the values the corresponding states. + */ private[this] val states = mutable.Map[P, ComputationState]() def analyze(data: P): PropertyComputationResult = { From e4b7cb074009660d2aa9b3a151345edc0eaf80de Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 10 Dec 2018 11:18:42 +0100 Subject: [PATCH 078/316] Removed unnecessary code and formatted the file. --- .../string_definition/LocalStringDefinitionAnalysis.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 9a37680c01..094f103868 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -36,10 +36,6 @@ import org.opalj.tac.TACStmts import scala.collection.mutable import scala.collection.mutable.ListBuffer -class StringTrackingAnalysisContext( - val stmts: Array[Stmt[V]] -) - /** * LocalStringDefinitionAnalysis processes a read operation of a local string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. @@ -253,8 +249,8 @@ sealed trait LocalStringDefinitionAnalysisScheduler extends ComputationSpecifica * Executor for the lazy analysis. */ object LazyStringDefinitionAnalysis - extends LocalStringDefinitionAnalysisScheduler - with FPCFLazyAnalysisScheduler { + extends LocalStringDefinitionAnalysisScheduler + with FPCFLazyAnalysisScheduler { final override def startLazily( p: SomeProject, ps: PropertyStore, unused: Null From cf4e52938c8179627fa90488c54d0a981dcf2a1b Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 10 Dec 2018 20:07:26 +0100 Subject: [PATCH 079/316] Started implementing a tool that analyzes a project (like the JDK) for those reflective calls where strings are input and then approximates these strings values using the string definition analysis. --- .../info/StringAnalysisReflectiveCalls.scala | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala new file mode 100644 index 0000000000..d979661039 --- /dev/null +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -0,0 +1,111 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.support.info + +import java.net.URL + +import org.opalj.br.analyses.BasicReport +import org.opalj.br.analyses.DefaultOneStepAnalysis +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.ReportableAnalysisResult +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.FPCFAnalysesManagerKey +import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.PropertyStoreKey +import org.opalj.tac.Assignment +import org.opalj.tac.ExprStmt +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.VirtualFunctionCall + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +/** + * Analyzes a project for calls provided by the Java Reflection API and tries to determine which + * string values are / could be passed to these calls. + *

+ * Currently, this runner supports / handles the following reflective calls: + *

    + *
  • `Class.forName(string)`
  • + *
  • `Class.forName(string, boolean, classLoader)`
  • + *
  • `Class.getField(string)`
  • + *
  • `Class.getDeclaredField(string)`
  • + *
  • `Class.getMethod(String, Class[])`
  • + *
  • `Class.getDeclaredMethod(String, Class[])`
  • + *
+ * + * @author Patrick Mell + */ +object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { + + override def title: String = "String Analysis for Reflective Calls" + + override def description: String = { + "Finds calls to methods provided by the Java Reflection API and tries to resolve passed "+ + "string values" + } + + override def doAnalyze( + project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean + ): ReportableAnalysisResult = { + implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + project.get(FPCFAnalysesManagerKey).runAll(LazyStringDefinitionAnalysis) + + // Stores the obtained results for each supported reflective operation + val countMap = mutable.Map[String, ListBuffer[StringConstancyInformation]]( + "forName" → ListBuffer(), + "getField" → ListBuffer(), + "getDeclaredField" → ListBuffer(), + "getMethod" → ListBuffer(), + "getDeclaredMethod" → ListBuffer() + ) + + val tacProvider = project.get(SimpleTACAIKey) + project.allMethodsWithBody.foreach { m ⇒ + val stmts = tacProvider(m).stmts + stmts.foreach { + // Capture the Class.forName calls + case Assignment(_, _, expr) if expr.isInstanceOf[StaticFunctionCall[V]] ⇒ + val sfc = expr.asInstanceOf[StaticFunctionCall[V]] + val fqClassName = sfc.declaringClass.toJava + if (countMap.contains(sfc.name) && fqClassName == "java.lang.Class") { + val duvar = sfc.params.head.asVar + propertyStore((List(duvar), m), StringConstancyProperty.key) match { + case FinalEP(_, prop) ⇒ + countMap(sfc.name).appendAll(prop.stringConstancyInformation) + case _ ⇒ + } + } + // Capture all other reflective calls + case ExprStmt(_, expr) ⇒ + expr match { + case vfc: VirtualFunctionCall[V] ⇒ + // Make sure we really deal with a call from the reflection API + if (countMap.contains(vfc.name) && + vfc.descriptor.returnType.toJava.contains("java.lang.reflect.")) { + // String argument is always the first one + val duvar = vfc.params.head.asVar + propertyStore((List(duvar), m), StringConstancyProperty.key) match { + case FinalEP(_, prop) ⇒ + countMap(vfc.name).appendAll( + prop.stringConstancyInformation + ) + case _ ⇒ + } + } + case _ ⇒ + } + case _ ⇒ + } + } + + val report = ListBuffer[String]("Results:") + // TODO: Define what the report shall look like + BasicReport(report) + } + +} From 7cf69f37fec46945e3bbf2f4df56e024c2449969 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 10 Dec 2018 20:14:27 +0100 Subject: [PATCH 080/316] Extended the class documentation. --- .../opalj/fpcf/fixtures/string_definition/TestMethods.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 33fcf90444..89e5388f4f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -37,6 +37,10 @@ * Thus, you should avoid the following characters / strings to occur in "expectedStrings": * {*, ?, \w, |}. In the future, "expectedStrings" might be parsed back into a StringTree. Thus, to * be on the safe side, brackets should be avoided as well. + *

+ * On order to trigger the analysis for a particular string or String{Buffer, Builder} call the + * analyzeString method with the variable to be analyzed. It is legal to have multiple + * calls to analyzeString within the same test method. * * @author Patrick Mell */ From 9cab2810e68bc6fa7236a675294538f01d42d7d4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 12:21:14 +0100 Subject: [PATCH 081/316] Improved the analysis runner (it captures more relevant calls (now it should capture all relevant calls) and is much faster). --- .../info/StringAnalysisReflectiveCalls.scala | 150 ++++++++++++------ 1 file changed, 102 insertions(+), 48 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index d979661039..d4511d5265 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -7,20 +7,28 @@ import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project import org.opalj.br.analyses.ReportableAnalysisResult -import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.br.instructions.Instruction +import org.opalj.br.instructions.INVOKESTATIC +import org.opalj.br.MethodDescriptor +import org.opalj.br.ReferenceType +import org.opalj.br.instructions.INVOKEVIRTUAL +import org.opalj.br.Method import org.opalj.fpcf.FPCFAnalysesManagerKey import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.PropertyStoreKey +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.Assignment +import org.opalj.tac.Call import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall +import scala.annotation.switch import scala.collection.mutable import scala.collection.mutable.ListBuffer @@ -30,18 +38,29 @@ import scala.collection.mutable.ListBuffer *

* Currently, this runner supports / handles the following reflective calls: *

    - *
  • `Class.forName(string)`
  • - *
  • `Class.forName(string, boolean, classLoader)`
  • - *
  • `Class.getField(string)`
  • - *
  • `Class.getDeclaredField(string)`
  • - *
  • `Class.getMethod(String, Class[])`
  • - *
  • `Class.getDeclaredMethod(String, Class[])`
  • + *
  • `Class.forName(string)`
  • + *
  • `Class.forName(string, boolean, classLoader)`
  • + *
  • `Class.getField(string)`
  • + *
  • `Class.getDeclaredField(string)`
  • + *
  • `Class.getMethod(String, Class[])`
  • + *
  • `Class.getDeclaredMethod(String, Class[])`
  • *
* * @author Patrick Mell */ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { + private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] + + /** + * Stores all relevant method names of the Java Reflection API, i.e., those methods from the + * Reflection API that have at least one string argument and shall be considered by this + * analysis. + */ + private val relevantMethodNames = List( + "forName", "getField", "getDeclaredField", "getMethod", "getDeclaredMethod" + ) + override def title: String = "String Analysis for Reflective Calls" override def description: String = { @@ -49,60 +68,95 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { "string values" } + /** + * Taking the `declaringClass`, the `methodName` as well as the `methodDescriptor` into + * consideration, this function checks whether a method is relevant for this analysis. + * + * @note Internally, this method makes use of [[relevantMethodNames]]. A method can only be + * relevant if its name occurs in [[relevantMethodNames]]. + */ + private def isRelevantMethod( + declaringClass: ReferenceType, methodName: String, methodDescriptor: MethodDescriptor + ): Boolean = + relevantMethodNames.contains(methodName) && (declaringClass.toJava == "java.lang.Class" || + methodDescriptor.returnType.toJava.contains("java.lang.reflect.")) + + /** + * Helper function that checks whether an array of [[Instruction]]s contains at least one + * relevant method that is to be processed by `doAnalyze`. + */ + private def instructionsContainRelevantMethod(instructions: Array[Instruction]): Boolean = { + instructions.filter(_ != null).foldLeft(false) { (previous, nextInstr) ⇒ + previous || ((nextInstr.opcode: @switch) match { + case INVOKESTATIC.opcode ⇒ + val INVOKESTATIC(declClass, _, methodName, methodDescr) = nextInstr + isRelevantMethod(declClass, methodName, methodDescr) + case INVOKEVIRTUAL.opcode ⇒ + val INVOKEVIRTUAL(declClass, methodName, methodDescr) = nextInstr + isRelevantMethod(declClass, methodName, methodDescr) + case _ ⇒ false + }) + } + } + + /** + * This function is a wrapper function for processing a method. It checks whether the given + * `method`, is relevant at all, and if so uses the given function `call` to call the + * analysis using the property store, `ps`, to finally store it in the given `resultMap`. + */ + private def processFunctionCall( + ps: PropertyStore, method: Method, call: Call[V], resultMap: ResultMapType + ): Unit = { + if (isRelevantMethod(call.declaringClass, call.name, call.descriptor)) { + val duvar = call.params.head.asVar + ps((List(duvar), method), StringConstancyProperty.key) match { + case FinalEP(_, prop) ⇒ + resultMap(call.name).appendAll(prop.stringConstancyInformation) + case _ ⇒ + } + } + } + override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean ): ReportableAnalysisResult = { implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) project.get(FPCFAnalysesManagerKey).runAll(LazyStringDefinitionAnalysis) + val tacProvider = project.get(SimpleTACAIKey) // Stores the obtained results for each supported reflective operation - val countMap = mutable.Map[String, ListBuffer[StringConstancyInformation]]( - "forName" → ListBuffer(), - "getField" → ListBuffer(), - "getDeclaredField" → ListBuffer(), - "getMethod" → ListBuffer(), - "getDeclaredMethod" → ListBuffer() - ) + val resultMap: ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]]() + relevantMethodNames.foreach { resultMap(_) = ListBuffer() } + + identity(propertyStore) - val tacProvider = project.get(SimpleTACAIKey) project.allMethodsWithBody.foreach { m ⇒ - val stmts = tacProvider(m).stmts - stmts.foreach { - // Capture the Class.forName calls - case Assignment(_, _, expr) if expr.isInstanceOf[StaticFunctionCall[V]] ⇒ - val sfc = expr.asInstanceOf[StaticFunctionCall[V]] - val fqClassName = sfc.declaringClass.toJava - if (countMap.contains(sfc.name) && fqClassName == "java.lang.Class") { - val duvar = sfc.params.head.asVar - propertyStore((List(duvar), m), StringConstancyProperty.key) match { - case FinalEP(_, prop) ⇒ - countMap(sfc.name).appendAll(prop.stringConstancyInformation) + // To dramatically reduce the work of the tacProvider, quickly check if a method is + // relevant at all + if (instructionsContainRelevantMethod(m.body.get.instructions)) { + val stmts = tacProvider(m).stmts + stmts.foreach { stmt ⇒ + // Use the following switch to speed-up the whole process + (stmt.astID: @switch) match { + case Assignment.ASTID ⇒ stmt match { + case Assignment(_, _, c: StaticFunctionCall[V]) ⇒ + processFunctionCall(propertyStore, m, c, resultMap) + case Assignment(_, _, c: VirtualFunctionCall[V]) ⇒ + processFunctionCall(propertyStore, m, c, resultMap) + case _ ⇒ + } + case ExprStmt.ASTID ⇒ stmt match { + case ExprStmt(_, c: StaticFunctionCall[V]) ⇒ + processFunctionCall(propertyStore, m, c, resultMap) + case ExprStmt(_, c: VirtualFunctionCall[V]) ⇒ + processFunctionCall(propertyStore, m, c, resultMap) case _ ⇒ } - } - // Capture all other reflective calls - case ExprStmt(_, expr) ⇒ - expr match { - case vfc: VirtualFunctionCall[V] ⇒ - // Make sure we really deal with a call from the reflection API - if (countMap.contains(vfc.name) && - vfc.descriptor.returnType.toJava.contains("java.lang.reflect.")) { - // String argument is always the first one - val duvar = vfc.params.head.asVar - propertyStore((List(duvar), m), StringConstancyProperty.key) match { - case FinalEP(_, prop) ⇒ - countMap(vfc.name).appendAll( - prop.stringConstancyInformation - ) - case _ ⇒ - } - } case _ ⇒ } - case _ ⇒ + } } } - val report = ListBuffer[String]("Results:") // TODO: Define what the report shall look like BasicReport(report) From 40648b37b221addbccaed089312d2cd2f519bd87 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 14:49:45 +0100 Subject: [PATCH 082/316] Directly query the property store after a new analysis was started. --- .../LocalStringDefinitionAnalysis.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 094f103868..1a5174648f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -102,8 +102,13 @@ class LocalStringDefinitionAnalysis( if (dependentVars.nonEmpty) { val toAnalyze = (dependentVars.keys.toList, data._2) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) - dependees.put(toAnalyze, ep) - states.put(data, ComputationState(leanPaths, dependentVars, cfg)) + ep match { + case FinalEP(_, p) ⇒ + scis.appendAll(p.stringConstancyInformation) + case _ ⇒ + dependees.put(toAnalyze, ep) + states.put(data, ComputationState(leanPaths, dependentVars, cfg)) + } } else { scis.append(new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true)) } From d642f7505b056bfa9b25db05ff141b8815b3fd71 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 14:54:43 +0100 Subject: [PATCH 083/316] Refactored the pattern matching. --- .../InterpretationHandler.scala | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 0dde5c6c5e..0b19f99b3d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -56,28 +56,22 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { processedDefSites.append(defSite) stmts(defSite) match { - case Assignment(_, _, expr) if expr.isInstanceOf[StringConst] ⇒ - new StringConstInterpreter(cfg, this).interpret(expr.asStringConst) - case Assignment(_, _, expr) if expr.isInstanceOf[ArrayLoad[V]] ⇒ - new ArrayLoadInterpreter(cfg, this).interpret(expr.asArrayLoad) - case Assignment(_, _, expr) if expr.isInstanceOf[New] ⇒ - new NewInterpreter(cfg, this).interpret(expr.asNew) - case Assignment(_, _, expr) if expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ - new VirtualFunctionCallInterpreter(cfg, this).interpret(expr.asVirtualFunctionCall) - case Assignment(_, _, expr) if expr.isInstanceOf[StaticFunctionCall[V]] ⇒ - new StaticFunctionCallInterpreter(cfg, this).interpret(expr.asStaticFunctionCall) - case Assignment(_, _, expr) if expr.isInstanceOf[BinaryExpr[V]] ⇒ - new BinaryExprInterpreter(cfg, this).interpret(expr.asBinaryExpr) - case Assignment(_, _, expr) if expr.isInstanceOf[NonVirtualFunctionCall[V]] ⇒ - new NonVirtualFunctionCallInterpreter( - cfg, this - ).interpret(expr.asNonVirtualFunctionCall) - case ExprStmt(_, expr) ⇒ - expr match { - case vfc: VirtualFunctionCall[V] ⇒ - new VirtualFunctionCallInterpreter(cfg, this).interpret(vfc) - case _ ⇒ List() - } + case Assignment(_, _, expr: StringConst) ⇒ + new StringConstInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: ArrayLoad[V]) ⇒ + new ArrayLoadInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: New) ⇒ + new NewInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + new VirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ + new StaticFunctionCallInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: BinaryExpr[V]) ⇒ + new BinaryExprInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ + new NonVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ + new VirtualFunctionCallInterpreter(cfg, this).interpret(expr) case vmc: VirtualMethodCall[V] ⇒ new VirtualMethodCallInterpreter(cfg, this).interpret(vmc) case nvmc: NonVirtualMethodCall[V] ⇒ From e75f8fa9bf191eb25c03bff66dd44ee0d55b5b8e Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 19:50:25 +0100 Subject: [PATCH 084/316] The analysis did not always work correctly (see changed test case). I changed the the routine that detects dependees as well as the way the intermediate result(s) is handed to have the correct information at the end of the analysis. --- .../string_definition/TestMethods.java | 27 ++-------------- .../LocalStringDefinitionAnalysis.scala | 29 ++++++++++++----- .../InterpretationHandler.scala | 31 +++++++++++++++++-- .../StringConstancyInformation.scala | 4 +-- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 89e5388f4f..ff4bcf77f0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -590,8 +590,8 @@ public void ifConditionAppendsToString(String className) { @StringDefinitions( value = "checks if a string value with > 2 continuous appends and a second " + "StringBuilder is determined correctly", - expectedLevels = { CONSTANT }, - expectedStrings = { "java.langStringB." } + expectedLevels = { CONSTANT, CONSTANT }, + expectedStrings = { "B.", "java.langStringB." } ) public void directAppendConcatsWith2ndStringBuilder() { StringBuilder sb = new StringBuilder("java"); @@ -600,29 +600,8 @@ public void directAppendConcatsWith2ndStringBuilder() { sb2.append("."); sb.append("String"); sb.append(sb2.toString()); - analyzeString(sb.toString()); - } - - @StringDefinitions( - value = "checks if the case, where the value of a StringBuilder depends on the " - + "complex construction of a second StringBuilder is determined correctly.", - expectedLevels = { CONSTANT }, - expectedStrings = { "java.lang.(Object|Runtime)" } - ) - public void secondStringBuilderRead(String className) { - StringBuilder sbObj = new StringBuilder("Object"); - StringBuilder sbRun = new StringBuilder("Runtime"); - - StringBuilder sb1 = new StringBuilder(); - if (sb1.length() == 0) { - sb1.append(sbObj.toString()); - } else { - sb1.append(sbRun.toString()); - } - - StringBuilder sb2 = new StringBuilder("java.lang."); - sb2.append(sb1.toString()); analyzeString(sb2.toString()); + analyzeString(sb.toString()); } // @StringDefinitions( diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 1a5174648f..ce752c0b30 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -98,7 +98,7 @@ class LocalStringDefinitionAnalysis( val leanPaths = paths.makeLeanPath(nextUVar, stmts) // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(leanPaths, stmts, data._1) + val dependentVars = findDependentVars(leanPaths, stmts, List(nextUVar)) if (dependentVars.nonEmpty) { val toAnalyze = (dependentVars.keys.toList, data._2) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) @@ -127,7 +127,7 @@ class LocalStringDefinitionAnalysis( StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, dependees.values, - continuation(data, dependees.values) + continuation(data, dependees.values, scis) ) } else { Result(data, StringConstancyProperty(scis.toList)) @@ -139,10 +139,16 @@ class LocalStringDefinitionAnalysis( * * @param data The data that was passed to the `analyze` function. * @param dependees A list of dependencies that this analysis run depends on. + * @param currentResults If the result of other read operations has been computed (only in case + * the first value of the `data` given to the `analyze` function contains + * more than one value), pass it using this object in order not to lose + * it. * @return This function can either produce a final result or another intermediate result. */ private def continuation( - data: P, dependees: Iterable[EOptionP[Entity, Property]] + data: P, + dependees: Iterable[EOptionP[Entity, Property]], + currentResults: ListBuffer[StringConstancyInformation] )(eps: SomeEPS): PropertyComputationResult = { val relevantState = states.get(data) // For mapping the index of a FlatPathElement to StringConstancyInformation @@ -158,10 +164,11 @@ class LocalStringDefinitionAnalysis( val sci = new PathTransformer(relevantState.get.cfg).pathToStringTree( relevantState.get.computedLeanPath, fpe2Sci.toMap ).reduce(true) - Result(data, StringConstancyProperty(List(sci))) + currentResults.append(sci) + Result(data, StringConstancyProperty(currentResults.toList)) case IntermediateEP(_, lb, ub) ⇒ IntermediateResult( - data, lb, ub, dependees, continuation(data, dependees) + data, lb, ub, dependees, continuation(data, dependees, currentResults) ) case _ ⇒ NoResult } @@ -207,8 +214,8 @@ class LocalStringDefinitionAnalysis( } /** - * Takes a path, this should be the lean path of a [[Path]], as well as a context in the form of - * statements, stmts, and detects all dependees within `path`. ''Dependees'' are found by + * Takes a `path`, this should be the lean path of a [[Path]], as well as a context in the form + * of statements, `stmts`, and detects all dependees within `path`. Dependees are found by * looking at all elements in the path, and check whether the argument of an `append` call is a * value that stems from a `toString` call of a [[StringBuilder]] or [[StringBuffer]]. This * function then returns the found UVars along with the indices of those append statements. @@ -220,9 +227,15 @@ class LocalStringDefinitionAnalysis( path: Path, stmts: Array[Stmt[V]], ignore: List[V] ): mutable.LinkedHashMap[V, Int] = { val dependees = mutable.LinkedHashMap[V, Int]() + val ignoreNews = ignore.map { i ⇒ + InterpretationHandler.findNewOfVar(i, stmts) + }.distinct + identity(ignoreNews) + path.elements.foreach { nextSubpath ⇒ findDependeesAcc(nextSubpath, stmts, ListBuffer()).foreach { nextPair ⇒ - if (!ignore.contains(nextPair._1)) { + val newExprs = InterpretationHandler.findNewOfVar(nextPair._1, stmts) + if (!ignore.contains(nextPair._1) && !ignoreNews.contains(newExprs)) { dependees.put(nextPair._1, nextPair._2) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 0b19f99b3d..55d079145c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -165,8 +165,7 @@ object InterpretationHandler { * [[AbstractStringBuilder]]. * @param stmts A list of statements which will be used to lookup which one the initialization * is. - * @return Returns the definition site of the base object of the call. If something goes wrong, - * e.g., no initialization is found, ''None'' is returned. + * @return Returns the definition sites of the base object of the call. */ def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { // TODO: Check that we deal with an instance of AbstractStringBuilder @@ -193,6 +192,34 @@ object InterpretationHandler { defSites.sorted.toList } + /** + * Determines the [[New]] expressions that belongs to a given `duvar`. + * + * @param duvar The [[org.opalj.tac.DUVar]] to get the [[New]]s for. + * @param stmts The context to search in, e.g., the surrounding method. + * @return Returns all found [[New]] expressions. + */ + def findNewOfVar(duvar: V, stmts: Array[Stmt[V]]): List[New] = { + val news = ListBuffer[New]() + + // TODO: It might be that the search has to be extended to further cases + duvar.definedBy.foreach { ds ⇒ + stmts(ds) match { + // E.g., a call to `toString` + case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ + vfc.receiver.asVar.definedBy.foreach { innerDs ⇒ + stmts(innerDs) match { + case Assignment(_, _, expr: New) ⇒ news.append(expr) + case _ ⇒ + } + } + case _ ⇒ + } + } + + news.toList + } + /** * @return Returns a [[StringConstancyInformation]] element that describes an `int` value. * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 5159727a23..5fadd85d68 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -7,8 +7,8 @@ package org.opalj.fpcf.string_definition.properties * @author Patrick Mell */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value, - constancyType: StringConstancyType.Value, + constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, + constancyType: StringConstancyType.Value = StringConstancyType.APPEND, possibleStrings: String = "" ) From 707fa4ad94fd16cee596e17e70b1da00de0feda8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 14 Dec 2018 20:22:49 +0100 Subject: [PATCH 085/316] Added primitive support for field reads: Whenever a field (that is not "public static final") is read, it is not further analyzed but the lower bound is returned. However, fields marked as "public static final String..." can be analyzed (see added test methods). --- .../string_definition/TestMethods.java | 28 ++++++++++++ .../interpretation/FieldInterpreter.scala | 43 +++++++++++++++++++ .../InterpretationHandler.scala | 3 ++ 3 files changed, 74 insertions(+) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index ff4bcf77f0..b35b8b2ae3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -46,6 +46,9 @@ */ public class TestMethods { + private String someStringField = ""; + public static final String MY_CONSTANT = "mine"; + /** * This method represents the test method which is serves as the trigger point for the * {@link org.opalj.fpcf.LocalStringDefinitionTest} to know which string read operation to @@ -604,6 +607,31 @@ public void directAppendConcatsWith2ndStringBuilder() { analyzeString(sb.toString()); } + @StringDefinitions( + value = "an example that uses a non final field", + expectedLevels = { PARTIALLY_CONSTANT }, + expectedStrings = { "Field Value:\\w" } + ) + public void nonFinalFieldRead() { + StringBuilder sb = new StringBuilder("Field Value:"); + System.out.println(sb); + sb.append(someStringField); + analyzeString(sb.toString()); + } + + @StringDefinitions( + value = "an example that reads a public final static field; for these, the string " + + "information are available (at lease on modern compilers)", + expectedLevels = { CONSTANT }, + expectedStrings = { "Field Value:mine" } + ) + public void finalFieldRead() { + StringBuilder sb = new StringBuilder("Field Value:"); + System.out.println(sb); + sb.append(MY_CONSTANT); + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevels = {StringConstancyLevel.CONSTANT}, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala new file mode 100644 index 0000000000..386b357dd0 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.tac.Stmt +import org.opalj.br.cfg.CFG +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.tac.GetField +import org.opalj.tac.TACStmts + +/** + * The `FieldInterpreter` is responsible for processing [[GetField]]s. Currently, there is only + * primitive support for fields, i.e., they are not analyzed but a constant + * [[StringConstancyInformation]] is returned. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class FieldInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = GetField[V] + + /** + * Currently, fields are not interpreted. Thus, this function always returns a list with a + * single element consisting of [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + )) + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 55d079145c..527fdc6b36 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -11,6 +11,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr import org.opalj.tac.ExprStmt +import org.opalj.tac.GetField import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.NonVirtualMethodCall @@ -70,6 +71,8 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { new BinaryExprInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ new NonVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: GetField[V]) ⇒ + new FieldInterpreter(cfg, this).interpret(expr) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ new VirtualFunctionCallInterpreter(cfg, this).interpret(expr) case vmc: VirtualMethodCall[V] ⇒ From eb02e5457ffaf29616f5ad30bedf11c92a04ada4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 18 Dec 2018 23:06:28 +0100 Subject: [PATCH 086/316] Changed the analysis to only take one DUVar in the entity pair => Testing related files had to be changed. However, the test case 'secondStringBuilderRead' works now. --- .../string_definition/TestMethods.java | 431 ++++++++++++------ .../string_definition/StringDefinitions.java | 15 +- .../StringDefinitionsCollection.java | 28 ++ .../fpcf/LocalStringDefinitionTest.scala | 35 +- .../LocalStringDefinitionMatcher.scala | 75 ++- .../LocalStringDefinitionAnalysis.scala | 155 ++++--- .../analyses/string_definition/package.scala | 4 +- .../preprocessing/PathTransformer.scala | 11 +- .../properties/StringConstancyProperty.scala | 19 +- 9 files changed, 494 insertions(+), 279 deletions(-) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index b35b8b2ae3..0c9bbecce7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -2,6 +2,7 @@ package org.opalj.fpcf.fixtures.string_definition; import org.opalj.fpcf.properties.string_definition.StringDefinitions; +import org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection; import java.io.IOException; import java.nio.file.Files; @@ -61,10 +62,16 @@ public class TestMethods { public void analyzeString(String s) { } - @StringDefinitions( + @StringDefinitionsCollection( value = "read-only string variable, trivial case", - expectedLevels = { CONSTANT, CONSTANT }, - expectedStrings = { "java.lang.String", "java.lang.String" } + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.String" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.String" + ) + } ) public void constantStringReads() { analyzeString("java.lang.String"); @@ -73,10 +80,16 @@ public void constantStringReads() { analyzeString(className); } - @StringDefinitions( + @StringDefinitionsCollection( value = "checks if a string value with append(s) is determined correctly", - expectedLevels = { CONSTANT, CONSTANT }, - expectedStrings = { "java.lang.String", "java.lang.Object" } + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.String" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.Object" + ) + } ) public void simpleStringConcat() { String className1 = "java.lang."; @@ -92,32 +105,38 @@ public void simpleStringConcat() { analyzeString(className2); } - @StringDefinitions( + @StringDefinitionsCollection( value = "checks if a string value with > 2 continuous appends is determined correctly", - expectedLevels = { CONSTANT }, - expectedStrings = { "java.lang.String" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.String" + ) + }) public void directAppendConcats() { StringBuilder sb = new StringBuilder("java"); sb.append(".").append("lang").append(".").append("String"); analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "at this point, function call cannot be handled => DYNAMIC", - expectedLevels = { DYNAMIC }, - expectedStrings = { "\\w" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ) + }) public void fromFunctionCall() { String className = getStringBuilderClassName(); analyzeString(className); } - @StringDefinitions( + @StringDefinitionsCollection( value = "constant string + string from function call => PARTIALLY_CONSTANT", - expectedLevels = { PARTIALLY_CONSTANT }, - expectedStrings = { "java.lang.\\w" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "java.lang.\\w" + ) + }) public void fromConstantAndFunctionCall() { String className = "java.lang."; System.out.println(className); @@ -125,12 +144,14 @@ public void fromConstantAndFunctionCall() { analyzeString(className); } - @StringDefinitions( + @StringDefinitionsCollection( value = "array access with unknown index", - expectedLevels = { CONSTANT }, - expectedStrings = { "(java.lang.String|java.lang.StringBuilder|" - + "java.lang.System|java.lang.Runnable)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(java.lang.String|" + + "java.lang.StringBuilder|java.lang.System|java.lang.Runnable)" + ) + }) public void fromStringArray(int index) { String[] classes = { "java.lang.String", "java.lang.StringBuilder", @@ -141,11 +162,14 @@ public void fromStringArray(int index) { } } - @StringDefinitions( + @StringDefinitionsCollection( value = "a simple case where multiple definition sites have to be considered", - expectedLevels = { CONSTANT }, - expectedStrings = { "(java.lang.System|java.lang.Runtime)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.System|java.lang.Runtime)" + ) + }) public void multipleConstantDefSites(boolean cond) { String s; if (cond) { @@ -156,12 +180,16 @@ public void multipleConstantDefSites(boolean cond) { analyzeString(s); } - @StringDefinitions( + @StringDefinitionsCollection( value = "a more comprehensive case where multiple definition sites have to be " + "considered each with a different string generation mechanism", - expectedLevels = { DYNAMIC }, - expectedStrings = { "(java.lang.Object|\\w|java.lang.System|java.lang.\\w|\\w)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "(java.lang.Object|\\w|java.lang.System|" + + "java.lang.\\w|\\w)" + ) + }) public void multipleDefSites(int value) { String[] arr = new String[] { "java.lang.Object", getRuntimeClassName() }; @@ -186,11 +214,13 @@ public void multipleDefSites(int value) { analyzeString(s); } - @StringDefinitions( + @StringDefinitionsCollection( value = "a case where multiple optional definition sites have to be considered.", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b|c)?" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|c)?" + ) + }) public void multipleOptionalAppendSites(int value) { StringBuilder sb = new StringBuilder("a"); switch (value) { @@ -208,12 +238,17 @@ public void multipleOptionalAppendSites(int value) { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "if-else control structure which append to a string builder with an int expr " + "and an int", - expectedLevels = { DYNAMIC, DYNAMIC }, - expectedStrings = { "(x|[AnIntegerValue])", "([AnIntegerValue]|x)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "(x|[AnIntegerValue])" + ), + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "([AnIntegerValue]|x)" + ) + }) public void ifElseWithStringBuilderWithIntExpr() { StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); @@ -229,11 +264,16 @@ public void ifElseWithStringBuilderWithIntExpr() { analyzeString(sb2.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "if-else control structure which append to a string builder", - expectedLevels = { CONSTANT, CONSTANT }, - expectedStrings = { "(a|b)", "a(b|c)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(a|b)" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b|c)" + ) + }) public void ifElseWithStringBuilder1() { StringBuilder sb1; StringBuilder sb2 = new StringBuilder("a"); @@ -250,11 +290,13 @@ public void ifElseWithStringBuilder1() { analyzeString(sb2.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "if-else control structure which append to a string builder multiple times", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(bcd|xyz)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(bcd|xyz)" + ) + }) public void ifElseWithStringBuilder3() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); @@ -270,12 +312,17 @@ public void ifElseWithStringBuilder3() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "simple for loops with known and unknown bounds", - expectedLevels = { CONSTANT, CONSTANT }, - // Currently, the analysis does not support determining loop ranges => a(b)* - expectedStrings = { "a(b)*", "a(b)*" } - ) + stringDefinitions = { + // Currently, the analysis does not support determining loop ranges => a(b)* + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b)*" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b)*" + ) + }) public void simpleForLoopWithKnownBounds() { StringBuilder sb = new StringBuilder("a"); for (int i = 0; i < 10; i++) { @@ -291,11 +338,14 @@ public void simpleForLoopWithKnownBounds() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "if-else control structure within a for loop and with an append afterwards", - expectedLevels = { PARTIALLY_CONSTANT }, - expectedStrings = { "((x|[AnIntegerValue]))*yz" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "((x|[AnIntegerValue]))*yz" + ) + }) public void ifElseInLoopWithAppendAfterwards() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 20; i++) { @@ -310,11 +360,13 @@ public void ifElseInLoopWithAppendAfterwards() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "if control structure without an else", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b)?" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b)?" + ) + }) public void ifWithoutElse() { StringBuilder sb = new StringBuilder("a"); int i = new Random().nextInt(); @@ -324,12 +376,14 @@ public void ifWithoutElse() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "case with a nested loop where in the outer loop a StringBuilder is created " + "that is later read", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b)*" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b)*" + ) + }) public void nestedLoops(int range) { for (int i = 0; i < range; i++) { StringBuilder sb = new StringBuilder("a"); @@ -340,11 +394,14 @@ public void nestedLoops(int range) { } } - @StringDefinitions( + @StringDefinitionsCollection( value = "some example that makes use of a StringBuffer instead of a StringBuilder", - expectedLevels = { PARTIALLY_CONSTANT }, - expectedStrings = { "((x|[AnIntegerValue]))*yz" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "((x|[AnIntegerValue]))*yz" + ) + }) public void stringBufferExample() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 20; i++) { @@ -359,11 +416,13 @@ public void stringBufferExample() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "while-true example", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b)*" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b)*" + ) + }) public void whileWithBreak() { StringBuilder sb = new StringBuilder("a"); while (true) { @@ -375,11 +434,13 @@ public void whileWithBreak() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "an example with a non-while-true loop containing a break", - expectedLevels = { CONSTANT }, - expectedStrings = { "a(b)*" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "a(b)*" + ) + }) public void whileWithBreak(int i) { StringBuilder sb = new StringBuilder("a"); int j = 0; @@ -393,11 +454,17 @@ public void whileWithBreak(int i) { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "an extensive example with many control structures", - expectedLevels = { CONSTANT, PARTIALLY_CONSTANT }, - expectedStrings = { "(iv1|iv2): ", "(iv1|iv2): (great!)*(\\w)?" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(iv1|iv2): " + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + ) + }) public void extensive(boolean cond) { StringBuilder sb = new StringBuilder(); if (cond) { @@ -424,11 +491,13 @@ public void extensive(boolean cond) { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "an example with a throw (and no try-catch-finally)", - expectedLevels = { PARTIALLY_CONSTANT }, - expectedStrings = { "File Content:\\w" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:\\w" + ) + }) public void withThrow(String filename) throws IOException { StringBuilder sb = new StringBuilder("File Content:"); String data = new String(Files.readAllBytes(Paths.get(filename))); @@ -436,18 +505,25 @@ public void withThrow(String filename) throws IOException { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "case with a try-finally exception", // Currently, multiple expectedLevels and expectedStrings values are necessary because // the three-address code contains multiple calls to 'analyzeString' which are currently // not filtered out - expectedLevels = { - PARTIALLY_CONSTANT, PARTIALLY_CONSTANT, PARTIALLY_CONSTANT - }, - expectedStrings = { - "File Content:(\\w)?", "File Content:(\\w)?", "File Content:(\\w)?" - } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "File Content:(\\w)?" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "File Content:(\\w)?" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "File Content:(\\w)?" + ) + }) public void withException(String filename) { StringBuilder sb = new StringBuilder("File Content:"); try { @@ -459,15 +535,19 @@ public void withException(String filename) { } } - @StringDefinitions( + @StringDefinitionsCollection( value = "case with a try-catch-finally exception", - expectedLevels = { - PARTIALLY_CONSTANT, PARTIALLY_CONSTANT, PARTIALLY_CONSTANT - }, - expectedStrings = { - "=====(\\w|=====)", "=====(\\w|=====)", "=====(\\w|=====)" - } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + ) + }) public void tryCatchFinally(String filename) { StringBuilder sb = new StringBuilder("====="); try { @@ -480,11 +560,16 @@ public void tryCatchFinally(String filename) { } } - @StringDefinitions( + @StringDefinitionsCollection( value = "simple examples to clear a StringBuilder", - expectedLevels = { DYNAMIC, DYNAMIC }, - expectedStrings = { "\\w", "\\w" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ), + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ) + }) public void simpleClearExamples() { StringBuilder sb1 = new StringBuilder("init_value:"); sb1.setLength(0); @@ -499,11 +584,14 @@ public void simpleClearExamples() { analyzeString(sb2.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "a more advanced example with a StringBuilder#setLength to clear it", - expectedLevels = { CONSTANT }, - expectedStrings = { "(init_value:Hello, world!Goodbye|Goodbye)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(init_value:Hello, world!Goodbye|Goodbye)" + ) + }) public void advancedClearExampleWithSetLength(int value) { StringBuilder sb = new StringBuilder("init_value:"); if (value < 10) { @@ -515,11 +603,17 @@ public void advancedClearExampleWithSetLength(int value) { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "a simple and a little more advanced example with a StringBuilder#replace call", - expectedLevels = { DYNAMIC, PARTIALLY_CONSTANT }, - expectedStrings = { "\\w", "(init_value:Hello, world!Goodbye|\\wGoodbye)" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "(init_value:Hello, world!Goodbye|\\wGoodbye)" + ) + }) public void replaceExamples(int value) { StringBuilder sb1 = new StringBuilder("init_value"); sb1.replace(0, 5, "replaced_value"); @@ -535,11 +629,19 @@ public void replaceExamples(int value) { analyzeString(sb1.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "loops that use breaks and continues (or both)", - expectedLevels = { CONSTANT, CONSTANT, DYNAMIC }, - expectedStrings = { "abc((d)?)*", "", "((\\w)?)*" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "" + ), + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "((\\w)?)*" + ) + }) public void breakContinueExamples(int value) { StringBuilder sb1 = new StringBuilder("abc"); for (int i = 0; i < value; i++) { @@ -576,12 +678,14 @@ public void breakContinueExamples(int value) { analyzeString(sb3.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "an example where in the condition of an 'if', a string is appended to a " + "StringBuilder", - expectedLevels = { CONSTANT }, - expectedStrings = { "java.lang.Runtime" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.Runtime" + ) + }) public void ifConditionAppendsToString(String className) { StringBuilder sb = new StringBuilder(); if (sb.append("java.lang.Runtime").toString().equals(className)) { @@ -590,12 +694,17 @@ public void ifConditionAppendsToString(String className) { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "checks if a string value with > 2 continuous appends and a second " + "StringBuilder is determined correctly", - expectedLevels = { CONSTANT, CONSTANT }, - expectedStrings = { "B.", "java.langStringB." } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "B." + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.langStringB." + ) + }) public void directAppendConcatsWith2ndStringBuilder() { StringBuilder sb = new StringBuilder("java"); StringBuilder sb2 = new StringBuilder("B"); @@ -607,11 +716,41 @@ public void directAppendConcatsWith2ndStringBuilder() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( + value = "checks if the case, where the value of a StringBuilder depends on the " + + "complex construction of a second StringBuilder is determined correctly.", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "(Object|Runtime)" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)" + ) + }) + public void secondStringBuilderRead(String className) { + StringBuilder sbObj = new StringBuilder("Object"); + StringBuilder sbRun = new StringBuilder("Runtime"); + + StringBuilder sb1 = new StringBuilder(); + if (sb1.length() == 0) { + sb1.append(sbObj.toString()); + } else { + sb1.append(sbRun.toString()); + } + + analyzeString(sb1.toString()); + StringBuilder sb2 = new StringBuilder("java.lang."); + sb2.append(sb1.toString()); + analyzeString(sb2.toString()); + } + + @StringDefinitionsCollection( value = "an example that uses a non final field", - expectedLevels = { PARTIALLY_CONSTANT }, - expectedStrings = { "Field Value:\\w" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "Field Value:\\w" + ) + }) public void nonFinalFieldRead() { StringBuilder sb = new StringBuilder("Field Value:"); System.out.println(sb); @@ -619,12 +758,14 @@ public void nonFinalFieldRead() { analyzeString(sb.toString()); } - @StringDefinitions( + @StringDefinitionsCollection( value = "an example that reads a public final static field; for these, the string " + "information are available (at lease on modern compilers)", - expectedLevels = { CONSTANT }, - expectedStrings = { "Field Value:mine" } - ) + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "Field Value:mine" + ) + }) public void finalFieldRead() { StringBuilder sb = new StringBuilder("Field Value:"); System.out.println(sb); @@ -632,9 +773,33 @@ public void finalFieldRead() { analyzeString(sb.toString()); } + // @StringDefinitionsCollection( + // value = "A case with a criss-cross append on two StringBuilders", + // stringDefinitions = { + // @StringDefinitions( + // expectedLevel = CONSTANT, expectedStrings = "Object(Runtime)?" + // ), + // @StringDefinitions( + // expectedLevel = CONSTANT, expectedStrings = "Runtime(Object)?" + // ) + // }) + // public void crissCrossExample(String className) { + // StringBuilder sbObj = new StringBuilder("Object"); + // StringBuilder sbRun = new StringBuilder("Runtime"); + // + // if (className.length() == 0) { + // sbRun.append(sbObj.toString()); + // } else { + // sbObj.append(sbRun.toString()); + // } + // + // analyzeString(sbObj.toString()); + // analyzeString(sbRun.toString()); + // } + // @StringDefinitions( // value = "a case with a switch with missing breaks", - // expectedLevels = {StringConstancyLevel.CONSTANT}, + // expectedLevel = StringConstancyLevel.CONSTANT}, // expectedStrings ={ "a(bc|c)?" } // ) // public void switchWithMissingBreak(int value) { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index aac88b238e..6047ce06b3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -20,26 +20,21 @@ @PropertyValidator(key = "StringConstancy", validator = LocalStringDefinitionMatcher.class) @Documented @Retention(RetentionPolicy.CLASS) -@Target({ ElementType.METHOD }) +@Target({ ElementType.ANNOTATION_TYPE }) public @interface StringDefinitions { /** - * A short reasoning of this property. - */ - String value() default "N/A"; - - /** - * This value determines the expected levels of freedom for a string field or local variable to + * This value determines the expected level of freedom for a local variable to * be changed. */ - StringConstancyLevel[] expectedLevels(); + StringConstancyLevel expectedLevel(); /** - * A regexp like string that describes the elements that are expected. For the rules, refer to + * A regexp like string that describes the element(s) that are expected. For the rules, refer to * {@link org.opalj.fpcf.string_definition.properties.StringTreeElement}. * For example, "(* | (hello | world)^5)" describes a string which can 1) either be any string * or 2) a five time concatenation of "hello" and/or "world". */ - String[] expectedStrings(); + String expectedStrings(); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java new file mode 100644 index 0000000000..c05352d056 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java @@ -0,0 +1,28 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.string_definition; + +import java.lang.annotation.*; + +/** + * A test method can contain > 1 triggers for analyzing a variable. Thus, multiple results are + * expected. This annotation is a wrapper for these expected results. For further information see + * {@link StringDefinitions}. + * + * @author Patrick Mell + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface StringDefinitionsCollection { + + /** + * A short reasoning of this property. + */ + String value() default "N/A"; + + /** + * The expected results in the correct order. + */ + StringDefinitions[] stringDefinitions(); + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala index 8a06e1ba1f..39fdeba571 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -8,6 +8,7 @@ import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method import org.opalj.br.cfg.CFG +import org.opalj.br.Annotations import org.opalj.fpcf.analyses.cg.V import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis import org.opalj.fpcf.analyses.string_definition.LocalStringDefinitionAnalysis @@ -69,6 +70,19 @@ class LocalStringDefinitionTest extends PropertiesTest { private def isStringUsageAnnotation(a: Annotation): Boolean = a.annotationType.toJavaClass.getName == LocalStringDefinitionTest.fqStringDefAnnotation + /** + * Extracts a `StringDefinitions` annotation from a `StringDefinitionsCollection` annotation. + * Make sure that you pass an instance of `StringDefinitionsCollection` and that the element at + * the given index really exists. Otherwise an exception will be thrown. + * + * @param a The `StringDefinitionsCollection` to extract a `StringDefinitions` from. + * @param index The index of the element from the `StringDefinitionsCollection` annotation to + * get. + * @return Returns the desired `StringDefinitions` annotation. + */ + private def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = + a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation + describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { val p = Project(getRelevantProjectFiles, Array[File]()) val ps = p.get(org.opalj.fpcf.PropertyStoreKey) @@ -78,7 +92,7 @@ class LocalStringDefinitionTest extends PropertiesTest { LazyStringDefinitionAnalysis.schedule(ps, null) // We need a "method to entity" matching for the evaluation (see further below) - val m2e = mutable.HashMap[Method, (Entity, Method)]() + val m2e = mutable.HashMap[Method, Entity]() val tacProvider = p.get(DefaultTACAIKey) p.allMethodsWithBody.filter { @@ -88,17 +102,24 @@ class LocalStringDefinitionTest extends PropertiesTest { } foreach { m ⇒ extractUVars(tacProvider(m).cfg).foreach { uvar ⇒ if (!m2e.contains(m)) { - m2e += (m → Tuple2(ListBuffer(uvar), m)) + m2e += m → ListBuffer(uvar) } else { - m2e(m)._1.asInstanceOf[ListBuffer[V]].append(uvar) + m2e(m).asInstanceOf[ListBuffer[V]].append(uvar) } + ps.force((uvar, m), StringConstancyProperty.key) } - ps.force((m2e(m)._1.asInstanceOf[ListBuffer[V]].toList, m), StringConstancyProperty.key) } // As entity, we need not the method but a tuple (DUVar, Method), thus this transformation - val eas = methodsWithAnnotations(p).map { next ⇒ - Tuple3(m2e(next._1), next._2, next._3) + val eas = methodsWithAnnotations(p).filter(am ⇒ m2e.contains(am._1)).flatMap { am ⇒ + m2e(am._1).asInstanceOf[ListBuffer[V]].zipWithIndex.map { + case (duvar, index) ⇒ + Tuple3( + (duvar, am._1), + { s: String ⇒ s"${am._2(s)} (#$index)" }, + List(getStringDefinitionsFromCollection(am._3, index)) + ) + } } validateProperties( TestContext(p, ps, Set(new LocalStringDefinitionAnalysis(p))), @@ -111,7 +132,7 @@ class LocalStringDefinitionTest extends PropertiesTest { object LocalStringDefinitionTest { - val fqStringDefAnnotation = "org.opalj.fpcf.properties.string_definition.StringDefinitions" + val fqStringDefAnnotation = "org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection" val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_definition.TestMethods" // The name of the method from which to extract DUVars to analyze val nameTestMethod = "analyzeString" diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index 26b5f07be9..e100f2b85a 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -3,16 +3,11 @@ package org.opalj.fpcf.properties.string_definition import org.opalj.br.analyses.Project import org.opalj.br.AnnotationLike -import org.opalj.br.EnumValue import org.opalj.br.ObjectType -import org.opalj.br.StringValue -import org.opalj.collection.immutable.RefArray import org.opalj.fpcf.properties.AbstractPropertyMatcher import org.opalj.fpcf.Property import org.opalj.fpcf.properties.StringConstancyProperty -import scala.collection.mutable.ListBuffer - /** * Matches local variable's `StringConstancy` property. The match is successful if the * variable has a constancy level that matches its actual usage and the expected values are present. @@ -22,28 +17,36 @@ import scala.collection.mutable.ListBuffer class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { /** - * Returns the constancy levels specified in the annotation as a list of lower-cased strings. + * @param a An annotation like of type + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * @return Returns the constancy level specified in the annotation as a string. In case an + * annotation other than StringDefinitions is passed, an [[IllegalArgumentException]] + * will be thrown (since it cannot be processed). */ - private def getExpectedConstancyLevels(a: AnnotationLike): List[String] = - a.elementValuePairs.find(_.name == "expectedLevels") match { - case Some(el) ⇒ - el.value.asArrayValue.values.asInstanceOf[RefArray[EnumValue]].map { - ev: EnumValue ⇒ ev.constName.toLowerCase - }.toList - case None ⇒ List() + private def getConstancyLevel(a: AnnotationLike): String = { + a.elementValuePairs.find(_.name == "expectedLevel") match { + case Some(el) ⇒ el.value.asEnumValue.constName + case None ⇒ throw new IllegalArgumentException( + "Can only extract the constancy level from a StringDefinitions annotation" + ) } + } /** - * Returns the expected strings specified in the annotation as a list. + * @param a An annotation like of type + * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * @return Returns the ''expectedStrings'' value from the annotation. In case an annotation + * other than StringDefinitions is passed, an [[IllegalArgumentException]] will be + * thrown (since it cannot be processed). */ - private def getExpectedStrings(a: AnnotationLike): List[String] = + private def getExpectedStrings(a: AnnotationLike): String = { a.elementValuePairs.find(_.name == "expectedStrings") match { - case Some(el) ⇒ - el.value.asArrayValue.values.asInstanceOf[RefArray[StringValue]].map { - sc: StringValue ⇒ sc.value - }.toList - case None ⇒ List() + case Some(el) ⇒ el.value.asStringValue.value + case None ⇒ throw new IllegalArgumentException( + "Can only extract the possible strings from a StringDefinitions annotation" + ) } + } /** * @inheritdoc @@ -55,33 +58,23 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { a: AnnotationLike, properties: Traversable[Property] ): Option[String] = { - val actLevels = ListBuffer[String]() - val actStrings = ListBuffer[String]() - if (properties.nonEmpty) { - properties.head match { - case prop: StringConstancyProperty ⇒ - prop.stringConstancyInformation.foreach { nextSci ⇒ - actLevels.append(nextSci.constancyLevel.toString.toLowerCase) - actStrings.append(nextSci.possibleStrings.toString) - } - case _ ⇒ - } + var actLevel = "" + var actString = "" + properties.head match { + case prop: StringConstancyProperty ⇒ + val sci = prop.stringConstancyInformation + actLevel = sci.constancyLevel.toString.toLowerCase + actString = sci.possibleStrings + case _ ⇒ } - val expLevels = getExpectedConstancyLevels(a) + val expLevel = getConstancyLevel(a).toLowerCase val expStrings = getExpectedStrings(a) - val errorMsg = s"Levels: ${expLevels.mkString("{", ",", "}")}, "+ - s"Strings: ${expStrings.mkString("{", ",", "}")}" + val errorMsg = s"Level: $expLevel, Strings: $expStrings" - // The lists need to have the same sizes and need to match element-wise - if (actLevels.size != expLevels.size || actStrings.size != expStrings.size) { + if (expLevel != actLevel || expStrings != actString) { return Some(errorMsg) } - for (i ← actLevels.indices) { - if (expLevels(i) != actLevels(i) || expStrings(i) != actStrings(i)) { - return Some(errorMsg) - } - } None } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index ce752c0b30..8a4a3eb144 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -60,65 +60,66 @@ class LocalStringDefinitionAnalysis( * have all required information ready for a final result. */ private case class ComputationState( - // The lean path that was computed - computedLeanPath: Path, - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - var2IndexMapping: mutable.LinkedHashMap[V, Int], - // The control flow graph on which the computedLeanPath is based - cfg: CFG[Stmt[V], TACStmts[V]] + // The lean path that was computed + computedLeanPath: Path, + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + var2IndexMapping: mutable.LinkedHashMap[V, Int], + // A mapping from values of FlatPathElements to StringConstancyInformation + fpe2sci: mutable.Map[Int, StringConstancyInformation], + // The control flow graph on which the computedLeanPath is based + cfg: CFG[Stmt[V], TACStmts[V]] ) - /** - * As executions of this analysis can be nested (since it may start itself), there might be - * several states to capture. In order to do so and enable each analysis instance to access its - * information, a map is used where the keys are the values fed into the analysis (which - * uniquely identify an analysis run) and the values the corresponding states. - */ - private[this] val states = mutable.Map[P, ComputationState]() - def analyze(data: P): PropertyComputationResult = { - // scis stores the final StringConstancyInformation - val scis = ListBuffer[StringConstancyInformation]() + // sci stores the final StringConstancyInformation (if it can be determined now at all) + var sci = StringConstancyProperty.lowerBound.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) val stmts = tacProvider(data._2).stmts val cfg = tacProvider(data._2).cfg - // If not empty, this routine can only produce an intermediate result + val uvar = data._1 + val defSites = uvar.definedBy.toArray.sorted + val expr = stmts(defSites.head).asAssignment.expr + val pathFinder: AbstractPathFinder = new DefaultPathFinder() + + // If not empty, this very routine can only produce an intermediate result val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() + // state will be set to a non-null value if this analysis needs to call other analyses / + // itself; only in the case it calls itself, will state be used, thus, it is valid to + // initialize it with null + var state: ComputationState = null - data._1.foreach { nextUVar ⇒ - val defSites = nextUVar.definedBy.toArray.sorted - val expr = stmts(defSites.head).asAssignment.expr - val pathFinder: AbstractPathFinder = new DefaultPathFinder() - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - val initDefSites = InterpretationHandler.findDefSiteOfInit( - expr.asVirtualFunctionCall, stmts - ) - val paths = pathFinder.findPaths(initDefSites, cfg) - val leanPaths = paths.makeLeanPath(nextUVar, stmts) + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + val initDefSites = InterpretationHandler.findDefSiteOfInit( + expr.asVirtualFunctionCall, stmts + ) + val paths = pathFinder.findPaths(initDefSites, cfg) + val leanPaths = paths.makeLeanPath(uvar, stmts) - // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(leanPaths, stmts, List(nextUVar)) - if (dependentVars.nonEmpty) { - val toAnalyze = (dependentVars.keys.toList, data._2) + // Find DUVars, that the analysis of the current entity depends on + val dependentVars = findDependentVars(leanPaths, stmts, List(uvar)) + if (dependentVars.nonEmpty) { + dependentVars.keys.foreach { nextVar ⇒ + val toAnalyze = (nextVar, data._2) + val fpe2sci = mutable.Map[Int, StringConstancyInformation]() + state = ComputationState(leanPaths, dependentVars, fpe2sci, cfg) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { - case FinalEP(_, p) ⇒ - scis.appendAll(p.stringConstancyInformation) + case FinalEP(e, p) ⇒ + return processFinalEP(data, dependees.values, state, e, p) case _ ⇒ dependees.put(toAnalyze, ep) - states.put(data, ComputationState(leanPaths, dependentVars, cfg)) } - } else { - scis.append(new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true)) } - } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings - else { - val interHandler = InterpretationHandler(cfg) - scis.append(StringConstancyInformation.reduceMultiple( - nextUVar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList - )) + } else { + sci = new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true) } + } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings + else { + val interHandler = InterpretationHandler(cfg) + sci = StringConstancyInformation.reduceMultiple( + uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList + ) } if (dependees.nonEmpty) { @@ -127,10 +128,42 @@ class LocalStringDefinitionAnalysis( StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, dependees.values, - continuation(data, dependees.values, scis) + continuation(data, dependees.values, state) ) } else { - Result(data, StringConstancyProperty(scis.toList)) + Result(data, StringConstancyProperty(sci)) + } + } + + /** + * `processFinalEP` is responsible for handling the case that the `propertyStore` outputs a + * [[FinalEP]]. + */ + private def processFinalEP( + data: P, + dependees: Iterable[EOptionP[Entity, Property]], + state: ComputationState, + e: Entity, + p: Property + ): PropertyComputationResult = { + // Add mapping information (which will be used for computing the final result) + val currentSci = p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + state.fpe2sci.put(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) + + val remDependees = dependees.filter(_.e != e) + if (remDependees.isEmpty) { + val finalSci = new PathTransformer(state.cfg).pathToStringTree( + state.computedLeanPath, state.fpe2sci.toMap + ).reduce(true) + Result(data, StringConstancyProperty(finalSci)) + } else { + IntermediateResult( + data, + StringConstancyProperty.upperBound, + StringConstancyProperty.lowerBound, + remDependees, + continuation(data, remDependees, state) + ) } } @@ -139,36 +172,21 @@ class LocalStringDefinitionAnalysis( * * @param data The data that was passed to the `analyze` function. * @param dependees A list of dependencies that this analysis run depends on. - * @param currentResults If the result of other read operations has been computed (only in case - * the first value of the `data` given to the `analyze` function contains - * more than one value), pass it using this object in order not to lose - * it. + * @param state The computation state (which was originally captured by `analyze` and possibly + * extended / updated by other methods involved in computing the final result. * @return This function can either produce a final result or another intermediate result. */ private def continuation( - data: P, - dependees: Iterable[EOptionP[Entity, Property]], - currentResults: ListBuffer[StringConstancyInformation] + data: P, + dependees: Iterable[EOptionP[Entity, Property]], + state: ComputationState )(eps: SomeEPS): PropertyComputationResult = { - val relevantState = states.get(data) - // For mapping the index of a FlatPathElement to StringConstancyInformation - val fpe2Sci = mutable.Map[Int, List[StringConstancyInformation]]() eps match { case FinalEP(e, p) ⇒ - val scis = p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - // Add mapping information - e.asInstanceOf[(List[V], _)]._1.asInstanceOf[List[V]].foreach { nextVar ⇒ - fpe2Sci.put(relevantState.get.var2IndexMapping(nextVar), scis) - } - // Compute final result - val sci = new PathTransformer(relevantState.get.cfg).pathToStringTree( - relevantState.get.computedLeanPath, fpe2Sci.toMap - ).reduce(true) - currentResults.append(sci) - Result(data, StringConstancyProperty(currentResults.toList)) + processFinalEP(data, dependees, state, e, p) case IntermediateEP(_, lb, ub) ⇒ IntermediateResult( - data, lb, ub, dependees, continuation(data, dependees, currentResults) + data, lb, ub, dependees, continuation(data, dependees, state) ) case _ ⇒ NoResult } @@ -230,7 +248,6 @@ class LocalStringDefinitionAnalysis( val ignoreNews = ignore.map { i ⇒ InterpretationHandler.findNewOfVar(i, stmts) }.distinct - identity(ignoreNews) path.elements.foreach { nextSubpath ⇒ findDependeesAcc(nextSubpath, stmts, ListBuffer()).foreach { nextPair ⇒ @@ -267,8 +284,8 @@ sealed trait LocalStringDefinitionAnalysisScheduler extends ComputationSpecifica * Executor for the lazy analysis. */ object LazyStringDefinitionAnalysis - extends LocalStringDefinitionAnalysisScheduler - with FPCFLazyAnalysisScheduler { + extends LocalStringDefinitionAnalysisScheduler + with FPCFLazyAnalysisScheduler { final override def startLazily( p: SomeProject, ps: PropertyStore, unused: Null diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala index 6ed3282711..c2f86842a0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala @@ -19,8 +19,8 @@ package object string_definition { /** * [[LocalStringDefinitionAnalysis]] processes a local variable within the context of a - * particular context, i.e., the method in which it is declared and used. + * particular context, i.e., the method in which it is used. */ - type P = (List[V], Method) + type P = (V, Method) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 64f1911909..1ebcbf1f07 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -35,13 +35,12 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * Accumulator function for transforming a path into a StringTree element. */ private def pathToTreeAcc( - subpath: SubPath, fpe2Sci: Map[Int, List[StringConstancyInformation]] + subpath: SubPath, fpe2Sci: Map[Int, StringConstancyInformation] ): Option[StringTree] = { subpath match { case fpe: FlatPathElement ⇒ - val sciList = fpe2Sci.getOrElse( - fpe.element, exprHandler.processDefSite(fpe.element) - ) + val sciList = if (fpe2Sci.contains(fpe.element)) List(fpe2Sci(fpe.element)) else + exprHandler.processDefSite(fpe.element) sciList.length match { case 0 ⇒ None case 1 ⇒ Some(StringTreeConst(sciList.head)) @@ -127,8 +126,8 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { */ def pathToStringTree( path: Path, - fpe2Sci: Map[Int, List[StringConstancyInformation]] = Map.empty, - resetExprHandler: Boolean = true + fpe2Sci: Map[Int, StringConstancyInformation] = Map.empty, + resetExprHandler: Boolean = true ): StringTree = { val tree = path.elements.size match { case 1 ⇒ pathToTreeAcc(path.elements.head, fpe2Sci).get diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala index f7091e8e62..2ad2f6351a 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala @@ -17,17 +17,14 @@ sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformat } class StringConstancyProperty( - val stringConstancyInformation: List[StringConstancyInformation] + val stringConstancyInformation: StringConstancyInformation ) extends Property with StringConstancyPropertyMetaInformation { final def key: PropertyKey[StringConstancyProperty] = StringConstancyProperty.key override def toString: String = { - val levels = stringConstancyInformation.map( - _.constancyLevel.toString.toLowerCase - ).mkString("{", ",", "}") - val strings = stringConstancyInformation.map(_.possibleStrings).mkString("{", ",", "}") - s"Levels: $levels, Strings: $strings" + val level = stringConstancyInformation.constancyLevel.toString.toLowerCase + s"Level: $level, Possible Strings: ${stringConstancyInformation.possibleStrings}" } } @@ -49,25 +46,25 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { } def apply( - stringConstancyInformation: List[StringConstancyInformation] + stringConstancyInformation: StringConstancyInformation ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) /** * @return Returns the upper bound from a lattice-point of view. */ def upperBound: StringConstancyProperty = - StringConstancyProperty(List(StringConstancyInformation( + StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND - ))) + )) /** * @return Returns the lower bound from a lattice-point of view. */ def lowerBound: StringConstancyProperty = - StringConstancyProperty(List(StringConstancyInformation( + StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, StringConstancyInformation.UnknownWordSymbol - ))) + )) } From 0a36c3aefac0b27207236cd2755c086a83e92433 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 19 Dec 2018 12:32:08 +0100 Subject: [PATCH 087/316] Updated the findDependentVars routine to avoid an endless loop in case of cycles. --- .../LocalStringDefinitionAnalysis.scala | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 8a4a3eb144..f2cb82333a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -97,7 +97,7 @@ class LocalStringDefinitionAnalysis( val leanPaths = paths.makeLeanPath(uvar, stmts) // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(leanPaths, stmts, List(uvar)) + val dependentVars = findDependentVars(leanPaths, stmts, uvar) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar ⇒ val toAnalyze = (nextVar, data._2) @@ -199,10 +199,18 @@ class LocalStringDefinitionAnalysis( * [[FlatPathElement.element]] in which it occurs. */ private def findDependeesAcc( - subpath: SubPath, stmts: Array[Stmt[V]], foundDependees: ListBuffer[(V, Int)] - ): ListBuffer[(V, Int)] = { + subpath: SubPath, + stmts: Array[Stmt[V]], + target: V, + foundDependees: ListBuffer[(V, Int)], + hasTargetBeenSeen: Boolean + ): (ListBuffer[(V, Int)], Boolean) = { + var encounteredTarget = false subpath match { case fpe: FlatPathElement ⇒ + if (target.definedBy.contains(fpe.element)) { + encounteredTarget = true + } // For FlatPathElements, search for DUVars on which the toString method is called // and where these toString calls are the parameter of an append call stmts(fpe.element) match { @@ -221,13 +229,18 @@ class LocalStringDefinitionAnalysis( } case _ ⇒ } - foundDependees + (foundDependees, encounteredTarget) case npe: NestedPathElement ⇒ npe.element.foreach { nextSubpath ⇒ - findDependeesAcc(nextSubpath, stmts, foundDependees) + if (!encounteredTarget) { + val (_, seen) = findDependeesAcc( + nextSubpath, stmts, target, foundDependees, encounteredTarget + ) + encounteredTarget = seen + } } - foundDependees - case _ ⇒ foundDependees + (foundDependees, encounteredTarget) + case _ ⇒ (foundDependees, encounteredTarget) } } @@ -238,22 +251,27 @@ class LocalStringDefinitionAnalysis( * value that stems from a `toString` call of a [[StringBuilder]] or [[StringBuffer]]. This * function then returns the found UVars along with the indices of those append statements. * - * @note In order to make sure that a [[org.opalj.tac.DUVar]] does not depend on itself, pass a - * `ignore` list (elements in `ignore` will not be added to the dependees list). + * @note In order to make sure that a [[org.opalj.tac.DUVar]] does not depend on itself, pass + * this variable as `ignore`. */ private def findDependentVars( - path: Path, stmts: Array[Stmt[V]], ignore: List[V] + path: Path, stmts: Array[Stmt[V]], ignore: V ): mutable.LinkedHashMap[V, Int] = { val dependees = mutable.LinkedHashMap[V, Int]() - val ignoreNews = ignore.map { i ⇒ - InterpretationHandler.findNewOfVar(i, stmts) - }.distinct + val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) + var wasTargetSeen = false path.elements.foreach { nextSubpath ⇒ - findDependeesAcc(nextSubpath, stmts, ListBuffer()).foreach { nextPair ⇒ - val newExprs = InterpretationHandler.findNewOfVar(nextPair._1, stmts) - if (!ignore.contains(nextPair._1) && !ignoreNews.contains(newExprs)) { - dependees.put(nextPair._1, nextPair._2) + if (!wasTargetSeen) { + val (currentDeps, encounteredTarget) = findDependeesAcc( + nextSubpath, stmts, ignore, ListBuffer(), false + ) + wasTargetSeen = encounteredTarget + currentDeps.foreach { nextPair ⇒ + val newExprs = InterpretationHandler.findNewOfVar(nextPair._1, stmts) + if (ignore != nextPair._1 && ignoreNews != newExprs) { + dependees.put(nextPair._1, nextPair._2) + } } } } From 981474ef3beb28dabab870e52b701814b5ce8703 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 23 Dec 2018 11:16:35 +0100 Subject: [PATCH 088/316] Added support for the "crissCrossExample" test case that was activated. This required refinements in different routines. --- .../string_definition/TestMethods.java | 46 +++--- .../LocalStringDefinitionAnalysis.scala | 9 +- .../InterpretationHandler.scala | 13 +- .../preprocessing/DefaultPathFinder.scala | 15 +- .../preprocessing/Path.scala | 139 ++++++++++++++++-- .../preprocessing/PathTransformer.scala | 3 +- 6 files changed, 171 insertions(+), 54 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 0c9bbecce7..e810f816b4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -773,29 +773,29 @@ public void finalFieldRead() { analyzeString(sb.toString()); } - // @StringDefinitionsCollection( - // value = "A case with a criss-cross append on two StringBuilders", - // stringDefinitions = { - // @StringDefinitions( - // expectedLevel = CONSTANT, expectedStrings = "Object(Runtime)?" - // ), - // @StringDefinitions( - // expectedLevel = CONSTANT, expectedStrings = "Runtime(Object)?" - // ) - // }) - // public void crissCrossExample(String className) { - // StringBuilder sbObj = new StringBuilder("Object"); - // StringBuilder sbRun = new StringBuilder("Runtime"); - // - // if (className.length() == 0) { - // sbRun.append(sbObj.toString()); - // } else { - // sbObj.append(sbRun.toString()); - // } - // - // analyzeString(sbObj.toString()); - // analyzeString(sbRun.toString()); - // } + @StringDefinitionsCollection( + value = "A case with a criss-cross append on two StringBuilders", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "Object(Runtime)?" + ), + @StringDefinitions( + expectedLevel = CONSTANT, expectedStrings = "Runtime(Object)?" + ) + }) + public void crissCrossExample(String className) { + StringBuilder sbObj = new StringBuilder("Object"); + StringBuilder sbRun = new StringBuilder("Runtime"); + + if (className.length() == 0) { + sbRun.append(sbObj.toString()); + } else { + sbObj.append(sbRun.toString()); + } + + analyzeString(sbObj.toString()); + analyzeString(sbRun.toString()); + } // @StringDefinitions( // value = "a case with a switch with missing breaks", diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index f2cb82333a..6449193e9b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -63,7 +63,7 @@ class LocalStringDefinitionAnalysis( // The lean path that was computed computedLeanPath: Path, // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - var2IndexMapping: mutable.LinkedHashMap[V, Int], + var2IndexMapping: mutable.Map[V, Int], // A mapping from values of FlatPathElements to StringConstancyInformation fpe2sci: mutable.Map[Int, StringConstancyInformation], // The control flow graph on which the computedLeanPath is based @@ -147,9 +147,11 @@ class LocalStringDefinitionAnalysis( p: Property ): PropertyComputationResult = { // Add mapping information (which will be used for computing the final result) - val currentSci = p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + val retrievedProperty = p.asInstanceOf[StringConstancyProperty] + val currentSci = retrievedProperty.stringConstancyInformation state.fpe2sci.put(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) + // No more dependees => Return the result for this analysis run val remDependees = dependees.filter(_.e != e) if (remDependees.isEmpty) { val finalSci = new PathTransformer(state.cfg).pathToStringTree( @@ -190,7 +192,6 @@ class LocalStringDefinitionAnalysis( ) case _ ⇒ NoResult } - } /** @@ -264,7 +265,7 @@ class LocalStringDefinitionAnalysis( path.elements.foreach { nextSubpath ⇒ if (!wasTargetSeen) { val (currentDeps, encounteredTarget) = findDependeesAcc( - nextSubpath, stmts, ignore, ListBuffer(), false + nextSubpath, stmts, ignore, ListBuffer(), hasTargetBeenSeen = false ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair ⇒ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 527fdc6b36..23d435acdc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -205,17 +205,22 @@ object InterpretationHandler { def findNewOfVar(duvar: V, stmts: Array[Stmt[V]]): List[New] = { val news = ListBuffer[New]() - // TODO: It might be that the search has to be extended to further cases + // HINT: It might be that the search has to be extended to further cases duvar.definedBy.foreach { ds ⇒ stmts(ds) match { - // E.g., a call to `toString` + // E.g., a call to `toString` or `append` case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ vfc.receiver.asVar.definedBy.foreach { innerDs ⇒ stmts(innerDs) match { - case Assignment(_, _, expr: New) ⇒ news.append(expr) - case _ ⇒ + case Assignment(_, _, expr: New) ⇒ + news.append(expr) + case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + news.appendAll(findNewOfVar(expr.receiver.asVar, stmts)) + case _ ⇒ } } + case Assignment(_, _, newExpr: New) ⇒ + news.append(newExpr) case _ ⇒ } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index ad3c3a2fd8..6e53e6f472 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -113,9 +113,10 @@ class DefaultPathFinder extends AbstractPathFinder { else { // For loops var ref: NestedPathElement = nestedElementsRef.head - // Refine for conditionals + // Refine for conditionals and try-catch(-finally) ref.elementType match { - case Some(t) if t == NestedPathType.CondWithAlternative ⇒ + case Some(t) if t == NestedPathType.CondWithAlternative || + t == NestedPathType.TryCatchFinally ⇒ ref = ref.element(currSplitIndex.head).asInstanceOf[NestedPathElement] case _ ⇒ } @@ -190,11 +191,11 @@ class DefaultPathFinder extends AbstractPathFinder { } ifWithElse = false } - val outerNested = generateNestPathElement( - relevantNumSuccessors, - if (ifWithElse) NestedPathType.CondWithAlternative else - NestedPathType.CondWithoutAlternative - ) + + val outerNestedType = if (catchSuccessors.nonEmpty) NestedPathType.TryCatchFinally + else if (ifWithElse) NestedPathType.CondWithAlternative + else NestedPathType.CondWithoutAlternative + val outerNested = generateNestPathElement(relevantNumSuccessors, outerNestedType) numSplits.prepend(relevantNumSuccessors) currSplitIndex.prepend(0) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index cd10dbeb98..4c4b8b5b59 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -2,8 +2,10 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler import org.opalj.tac.Assignment import org.opalj.tac.DUVar +import org.opalj.tac.ExprStmt import org.opalj.tac.New import org.opalj.tac.Stmt import org.opalj.tac.VirtualFunctionCall @@ -52,6 +54,11 @@ object NestedPathType extends Enumeration { */ val CondWithoutAlternative: NestedPathType.Value = Value + /** + * This type is to mark `try-catch` or `try-catch-finally` constructs. + */ + val TryCatchFinally: NestedPathType.Value = Value + } /** @@ -102,34 +109,111 @@ case class Path(elements: List[SubPath]) { defAndUses.toList.sorted } + /** + * Takes a `subpath` and checks whether the given `element` is contained. This function does a + * deep search, i.e., will also find the element if it is contained within + * [[NestedPathElement]]s. + */ + private def containsPathElement(subpath: NestedPathElement, element: Int): Boolean = { + subpath.element.foldLeft(false) { (old: Boolean, nextSubpath: SubPath) ⇒ + old || (nextSubpath match { + case fpe: FlatPathElement ⇒ fpe.element == element + case npe: NestedPathElement ⇒ containsPathElement(npe, element) + // For the SubPath type (should never be the case, but the compiler wants it) + case _ ⇒ false + }) + } + } + + /** + * Takes a [[NestedPathElement]], `npe`, and an `endSite` and strips all branches that do not + * contain `endSite`. ''Stripping'' here means to clear the other branches. + * For example, assume `npe=[[3, 5], [7, 9]]` and `endSite=7`, the this function will return + * `[[], [7, 9]]`. This function can handle deeply nested [[NestedPathElement]] expressions as + * well. + */ + private def stripUnnecessaryBranches( + npe: NestedPathElement, endSite: Int + ): NestedPathElement = { + npe.element.foreach { + case innerNpe: NestedPathElement ⇒ + if (innerNpe.elementType.isEmpty) { + if (!containsPathElement(innerNpe, endSite)) { + innerNpe.element.clear() + } + } else { + stripUnnecessaryBranches(innerNpe, endSite) + } + case _ ⇒ + } + npe + } + /** * Accumulator function for transforming a path into its lean equivalent. This function turns - * [[NestedPathElement]]s into lean [[NestedPathElement]]s. In case a (sub) path is empty, - * `None` is returned and otherwise the lean (sub) path. + * [[NestedPathElement]]s into lean [[NestedPathElement]]s and is a helper function of + * [[makeLeanPath]]. + * + * @param toProcess The NestedPathElement to turn into its lean equivalent. + * @param siteMap Serves as a look-up table to include only elements that are of interest, in + * this case: That belong to some object. + * @param endSite `endSite` is an denotes an element which is sort of a border between elements + * to include into the lean path and which not to include. For example, if a read + * operation, which is of interest, occurs not at the end of the given `toProcess` + * path, the rest can be safely omitted (as the paths already are in a + * happens-before relationship). If all elements are included, pass an int value + * that is greater than the greatest index of the elements in `toProcess`. + * @param includeAlternatives For cases where an operation of interest happens within a branch + * of an `if-else` constructions , it is not necessary to include the + * other branches (as they are mutually exclusive anyway). + * `includeAlternatives = false` represents this behavior. However, + * sometimes it is desired to include all alternatives as in the case + * of `try-catch(-finally)` constructions). + * @return In case a (sub) path is empty, `None` is returned and otherwise the lean (sub) path. */ private def makeLeanPathAcc( - toProcess: NestedPathElement, siteMap: Map[Int, Unit.type] - ): Option[NestedPathElement] = { + toProcess: NestedPathElement, + siteMap: Map[Int, Unit.type], + endSite: Int, + includeAlternatives: Boolean = false + ): (Option[NestedPathElement], Boolean) = { val elements = ListBuffer[SubPath]() + var hasTargetBeenSeen = false + val isTryCatch = includeAlternatives || (toProcess.elementType.isDefined && + toProcess.elementType.get == NestedPathType.TryCatchFinally) toProcess.element.foreach { - case fpe: FlatPathElement ⇒ - if (siteMap.contains(fpe.element)) { + case fpe: FlatPathElement if !hasTargetBeenSeen ⇒ + if (siteMap.contains(fpe.element) && !hasTargetBeenSeen) { elements.append(fpe.copy()) } + if (fpe.element == endSite) { + hasTargetBeenSeen = true + } + case npe: NestedPathElement if isTryCatch ⇒ + val (leanedSubPath, _) = makeLeanPathAcc( + npe, siteMap, endSite, includeAlternatives = true + ) + if (leanedSubPath.isDefined) { + elements.append(leanedSubPath.get) + } case npe: NestedPathElement ⇒ - val nested = makeLeanPathAcc(npe, siteMap) - if (nested.isDefined) { - elements.append(nested.get) + if (!hasTargetBeenSeen) { + val (leanedSubPath, wasTargetSeen) = makeLeanPathAcc(npe, siteMap, endSite) + if (leanedSubPath.isDefined) { + elements.append(leanedSubPath.get) + } + if (wasTargetSeen) { + hasTargetBeenSeen = true + } } - // For the case the element is a SubPath (should never happen but the compiler want it) case _ ⇒ } if (elements.nonEmpty) { - Some(NestedPathElement(elements, toProcess.elementType)) + (Some(NestedPathElement(elements, toProcess.elementType)), hasTargetBeenSeen) } else { - None + (None, false) } } @@ -152,11 +236,21 @@ case class Path(elements: List[SubPath]) { * instance do not share any references. */ def makeLeanPath(obj: DUVar[ValueInformation], stmts: Array[Stmt[V]]): Path = { - // Transform the list into a map to have a constant access time - val siteMap = Map(getAllDefAndUseSites(obj, stmts) map { s ⇒ (s, Unit) }: _*) + val newOfObj = InterpretationHandler.findNewOfVar(obj, stmts) + // Transform the list of relevant sites into a map to have a constant access time + val siteMap = getAllDefAndUseSites(obj, stmts).filter { nextSite ⇒ + stmts(nextSite) match { + case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + newOfObj == InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) + case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ + newOfObj == InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) + case _ ⇒ true + } + }.map { s ⇒ (s, Unit) }.toMap val leanPath = ListBuffer[SubPath]() val endSite = obj.definedBy.head var reachedEndSite = false + elements.foreach { next ⇒ if (!reachedEndSite) { next match { @@ -166,7 +260,11 @@ case class Path(elements: List[SubPath]) { reachedEndSite = true } case npe: NestedPathElement ⇒ - val leanedPath = makeLeanPathAcc(npe, siteMap) + val (leanedPath, wasTargetSeen) = makeLeanPathAcc(npe, siteMap, endSite) + if (npe.elementType.isDefined && + npe.elementType.get != NestedPathType.TryCatchFinally) { + reachedEndSite = wasTargetSeen + } if (leanedPath.isDefined) { leanPath.append(leanedPath.get) } @@ -175,6 +273,17 @@ case class Path(elements: List[SubPath]) { } } + // If the last element is a conditional, keep only the relevant branch (the other is not + // necessary and stripping it simplifies further steps; explicitly exclude try-catch here!) + leanPath.last match { + case npe: NestedPathElement if npe.elementType.isDefined && + (npe.elementType.get != NestedPathType.TryCatchFinally) ⇒ + val newLast = stripUnnecessaryBranches(npe, endSite) + leanPath.remove(leanPath.size - 1) + leanPath.append(newLast) + case _ ⇒ + } + Path(leanPath.toList) } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala index 1ebcbf1f07..ea17c9e2bd 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala @@ -67,7 +67,8 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { }.filter(_.isDefined).map(_.get) if (processedSubPaths.nonEmpty) { npe.elementType.get match { - case NestedPathType.CondWithAlternative ⇒ + case NestedPathType.CondWithAlternative | + NestedPathType.TryCatchFinally ⇒ // In case there is only one element in the sub path, // transform it into a conditional element (as there is no // alternative) From 1b783b66e6ed6fcedd7d0e5e14b0380c069e2744 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 28 Dec 2018 12:39:51 +0100 Subject: [PATCH 089/316] Added a test case which uses a "throwable". This required minor changes in the path finding procedure. --- .../string_definition/TestMethods.java | 27 ++++++++++- .../preprocessing/AbstractPathFinder.scala | 22 ++++++++- .../preprocessing/DefaultPathFinder.scala | 48 +++++++++++-------- 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index e810f816b4..2e3291bd7b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -462,7 +462,7 @@ public void whileWithBreak(int i) { ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(iv1|iv2): (great!)*(\\w)?" + expectedStrings = "(iv1|iv2): ((great!)?)*(\\w)?" ) }) public void extensive(boolean cond) { @@ -560,6 +560,31 @@ public void tryCatchFinally(String filename) { } } + @StringDefinitionsCollection( + value = "case with a try-catch-finally throwable", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + ) + }) + public void tryCatchFinallyWithThrowable(String filename) { + StringBuilder sb = new StringBuilder("BOS:"); + try { + String data = new String(Files.readAllBytes(Paths.get(filename))); + sb.append(data); + } catch (Throwable t) { + sb.append(":EOS"); + } finally { + analyzeString(sb.toString()); + } + } + @StringDefinitionsCollection( value = "simple examples to clear a StringBuilder", stringDefinitions = { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 9d5d6e984d..c1c72ce0ee 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing +import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG import org.opalj.br.cfg.CFGNode @@ -63,7 +64,9 @@ trait AbstractPathFinder { // if (again, respect structures as produces by while-true loops) if (!belongsToLoopHeader) { loops.foreach { nextLoop ⇒ - if (!belongsToLoopHeader) { + // The second condition is to regard only those elements as headers which have a + // backedge + if (!belongsToLoopHeader && cfg.bb(site).asBasicBlock.predecessors.size > 1) { val start = nextLoop.head var end = start while (!cfg.code.instructions(end).isInstanceOf[If[V]]) { @@ -86,6 +89,23 @@ trait AbstractPathFinder { protected def isEndOfLoop(site: Int, loops: List[List[Int]]): Boolean = loops.foldLeft(false)((old: Boolean, nextLoop: List[Int]) ⇒ old || nextLoop.last == site) + /** + * Checks whether a given [[BasicBlock]] has one (or several) successors which have at least n + * predecessors. + * + * @param bb The basic block to check whether it has a successor with at least n predecessors. + * @param n The number of required predecessors. + * @return Returns ''true'' if ''bb'' has a successor which has at least ''n'' predecessors. + * + * @note This function regards as successors and predecessors only [[BasicBlock]]s. + */ + protected def hasSuccessorWithAtLeastNPredecessors(bb: BasicBlock, n: Int = 2): Boolean = + bb.successors.filter( + _.isInstanceOf[BasicBlock] + ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) + /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no * else block. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 6e53e6f472..3e837dd17b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -53,6 +53,9 @@ class DefaultPathFinder extends AbstractPathFinder { generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) numSplits.append(startSites.size) currSplitIndex.append(0) + outerNested.element.reverse.foreach { next ⇒ + nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) + } nestedElementsRef.append(outerNested) path.append(outerNested) } @@ -112,7 +115,7 @@ class DefaultPathFinder extends AbstractPathFinder { } // Within a nested structure => append to an inner element else { // For loops - var ref: NestedPathElement = nestedElementsRef.head + var ref: NestedPathElement = nestedElementsRef(currSplitIndex.head) // Refine for conditionals and try-catch(-finally) ref.elementType match { case Some(t) if t == NestedPathType.CondWithAlternative || @@ -124,11 +127,9 @@ class DefaultPathFinder extends AbstractPathFinder { } } - // Find all regular successors (excluding CatchNodes) val successors = bb.successors.filter { - case _: ExitNode ⇒ false - case cn: CatchNode ⇒ cn.catchType.isDefined - case _ ⇒ true + case _: ExitNode ⇒ false + case _ ⇒ true }.map { case cn: CatchNode ⇒ cn.handlerPC case s ⇒ s.nodeId @@ -145,25 +146,31 @@ class DefaultPathFinder extends AbstractPathFinder { // Clean a loop from the stacks if the end of a loop was reached if (loopEndingIndex != -1) { - numSplits.remove(loopEndingIndex) - currSplitIndex.remove(loopEndingIndex) + // For finding the corresponding element ref, we can use the loopEndingIndex; + // however, for numSplits and currSplitIndex we need to find the correct position in + // the array first; we do this by finding the first element with only one branch + // which will correspond to the loop + val deletePos = numSplits.indexOf(1) + numSplits.remove(deletePos) + currSplitIndex.remove(deletePos) nestedElementsRef.remove(loopEndingIndex) numBackedgesLoop.remove(0) backedgeLoopCounter.remove(0) - } - - // At the join point of a branching, do some housekeeping - if (currSplitIndex.nonEmpty && - ((bb.predecessors.size > 1 && !isLoopHeader) || hasSeenSuccessor)) { - currSplitIndex(0) += 1 - if (currSplitIndex.head == numSplits.head) { - numSplits.remove(0) - currSplitIndex.remove(0) - nestedElementsRef.remove(0) + } // For join points of branchings, do some housekeeping (explicitly excluding loops) + else if (currSplitIndex.nonEmpty && + (hasSuccessorWithAtLeastNPredecessors(bb) || hasSeenSuccessor)) { + if (nestedElementsRef.head.elementType.getOrElse(NestedPathType.TryCatchFinally) != + NestedPathType.Repetition) { + currSplitIndex(0) += 1 + if (currSplitIndex.head == numSplits.head) { + numSplits.remove(0) + currSplitIndex.remove(0) + nestedElementsRef.remove(0) + } } } - if (numSplits.nonEmpty && (bb.predecessors.size == 1)) { + if ((numSplits.nonEmpty || backedgeLoopCounter.nonEmpty) && (bb.predecessors.size == 1)) { // Within a conditional, prepend in order to keep the correct order val newStack = IntArrayStack.fromSeq(stack.reverse) newStack.push(IntArrayStack.fromSeq(successorsToAdd.reverse)) @@ -199,7 +206,10 @@ class DefaultPathFinder extends AbstractPathFinder { numSplits.prepend(relevantNumSuccessors) currSplitIndex.prepend(0) - nestedElementsRef.prepend(outerNested) + outerNested.element.reverse.foreach { next ⇒ + nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) + } + // nestedElementsRef.prepend(outerNested) appendSite.append(outerNested) } } From 46140ea70b73fec1d49e8c3f7560b7ddbafc30b9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 28 Dec 2018 21:30:03 +0100 Subject: [PATCH 090/316] Needed to minorly change how the StringAnalysisReflectiveCalls uses the analysis. --- .../org/opalj/support/info/StringAnalysisReflectiveCalls.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index d4511d5265..cab8d330e5 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -111,7 +111,7 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { val duvar = call.params.head.asVar ps((List(duvar), method), StringConstancyProperty.key) match { case FinalEP(_, prop) ⇒ - resultMap(call.name).appendAll(prop.stringConstancyInformation) + resultMap(call.name).append(prop.stringConstancyInformation) case _ ⇒ } } From 006ea0dd38dd6c380195a5379c8a48dbd0a5741d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20K=C3=BCbler?= Date: Tue, 7 Jan 2020 13:16:54 +0100 Subject: [PATCH 091/316] Added an analysis which collects which methods of a class are called within a project and how often. # Conflicts: # DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala --- .../support/info/ClassUsageAnalysis.scala | 179 ++++++------------ 1 file changed, 61 insertions(+), 118 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala index 4b871d887d..825f325e64 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala @@ -1,61 +1,65 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.support.info -import scala.annotation.switch - import java.net.URL -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger - -import scala.collection.mutable.ListBuffer -import org.opalj.log.GlobalLogContext -import org.opalj.value.ValueInformation import org.opalj.br.analyses.BasicReport +import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project -import org.opalj.br.analyses.ProjectAnalysisApplication import org.opalj.br.analyses.ReportableAnalysisResult +import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.Assignment import org.opalj.tac.Call -import org.opalj.tac.DUVar import org.opalj.tac.ExprStmt -import org.opalj.tac.LazyDetachedTACAIKey -import org.opalj.tac.NonVirtualMethodCall -import org.opalj.tac.StaticMethodCall -import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.VirtualFunctionCall + +import scala.annotation.switch +import scala.collection.mutable +import scala.collection.mutable.ListBuffer /** - * Analyzes a project for how a particular class is used within that project. Collects information - * on which methods are called on objects of that class as well as how often. + * Analyzes a project for how a particular class is used within that project. This means that this + * analysis collect information on which methods are called on objects of that class as well as how + * often. * - * The analysis can be configured by passing the following parameters: `class` (the class to - * analyze) and `granularity` (fine or coarse; defines which information will be gathered by an - * analysis run; this parameter is optional). For further information see - * [[ClassUsageAnalysis.analysisSpecificParametersDescription]]. + * Currently, the analysis can be configured by setting the two object variables + * [[ClassUsageAnalysis.className]] and [[ClassUsageAnalysis.isFineGrainedAnalysis]]. * * @author Patrick Mell - * @author Dominik Helm */ -object ClassUsageAnalysis extends ProjectAnalysisApplication { - - private type V = DUVar[ValueInformation] - - implicit val logContext: GlobalLogContext.type = GlobalLogContext +object ClassUsageAnalysis extends DefaultOneStepAnalysis { override def title: String = "Class Usage Analysis" override def description: String = { - "Analyzes a project for how a particular class is used within it, i.e., which methods "+ - "of instances of that class are called" + "Analysis a project for how a particular class is used, i.e., which methods are called "+ + "on it" } + /** + * The fully-qualified name of the class that is to be analyzed in a Java format, i.e., dots as + * package / class separators. + */ + private val className = "java.lang.StringBuilder" + + /** + * The analysis can run in two modes: Fine-grained or coarse-grained. Fine-grained means that + * two method are considered as the same only if their method descriptor is the same, i.e., this + * mode enables a differentiation between overloaded methods. + * The coarse-grained method, however, regards two method calls as the same if the class of the + * base object as well as the method name are equal, i.e., overloaded methods are not + * distinguished. + */ + private val isFineGrainedAnalysis = true + /** * Takes a [[Call]] and assembles the method descriptor for this call. The granularity is * determined by [[isFineGrainedAnalysis]]: For a fine-grained analysis, the returned string has * the format "[fully-qualified classname]#[method name]: [stringified method descriptor]" and * for a coarse-grained analysis: [fully-qualified classname]#[method name]. */ - private def assembleMethodDescriptor(call: Call[V], isFineGrainedAnalysis: Boolean): String = { + private def assembleMethodDescriptor(call: Call[V]): String = { val fqMethodName = s"${call.declaringClass.toJava}#${call.name}" if (isFineGrainedAnalysis) { val methodDescriptor = call.descriptor.toString @@ -66,112 +70,51 @@ object ClassUsageAnalysis extends ProjectAnalysisApplication { } /** - * Takes any [[Call]], checks whether the base object is of type [[className]] and if so, - * updates the passed map by adding the count of the corresponding method. The granularity for - * counting is determined by [[isFineGrainedAnalysis]]. + * Takes any function [[Call]], checks whether the base object is of type [[className]] and if + * so, updates the passed map by adding the count of the corresponding method. The granularity + * for counting is determined by [[isFineGrainedAnalysis]]. */ - private def processCall( - call: Call[V], - map: ConcurrentHashMap[String, AtomicInteger], - className: String, - isFineGrainedAnalysis: Boolean - ): Unit = { + private def processFunctionCall(call: Call[V], map: mutable.Map[String, Int]): Unit = { val declaringClassName = call.declaringClass.toJava if (declaringClassName == className) { - val methodDescriptor = assembleMethodDescriptor(call, isFineGrainedAnalysis) - if (map.putIfAbsent(methodDescriptor, new AtomicInteger(1)) != null) { - map.get(methodDescriptor).addAndGet(1) - } - } - } - - override def analysisSpecificParametersDescription: String = { - "-class= \n"+ - "[-granularity= (Default: coarse)]" - } - - /** - * The fully-qualified name of the class that is to be analyzed in a Java format, i.e., dots as - * package / class separators. - */ - private final val parameterNameForClass = "-class=" - - /** - * The analysis can run in two modes: Fine-grained or coarse-grained. Fine-grained means that - * two methods are considered equal iff their method descriptor is the same, i.e., this mode - * enables a differentiation between overloaded methods. - * The coarse-grained method, however, regards two method calls as the same if the class of the - * base object as well as the method name are equal, i.e., overloaded methods are not - * distinguished. - */ - private final val parameterNameForGranularity = "-granularity=" - - override def checkAnalysisSpecificParameters(parameters: Seq[String]): Traversable[String] = { - val remainingParameters = - parameters.filter { p ⇒ - !p.contains(parameterNameForClass) && !p.contains(parameterNameForGranularity) - } - super.checkAnalysisSpecificParameters(remainingParameters) - } - - /** - * Takes the parameters passed as program arguments, i.e., in the format - * "-[param name]=[value]", extracts the values and sets the corresponding object variables. - */ - private def getAnalysisParameters(parameters: Seq[String]): (String, Boolean) = { - val classParam = parameters.find(_.startsWith(parameterNameForClass)) - val className = if (classParam.isDefined) { - classParam.get.substring(classParam.get.indexOf("=") + 1) - } else { - throw new IllegalArgumentException("missing argument: -class") - } - - val granularityParam = parameters.find(_.startsWith(parameterNameForGranularity)) - val isFineGrainedAnalysis = - if (granularityParam.isDefined) { - granularityParam.get.substring(granularityParam.get.indexOf("=") + 1) match { - case "fine" ⇒ true - case "coarse" ⇒ false - case _ ⇒ - val msg = "incorrect argument: -granularity must be one of fine|coarse" - throw new IllegalArgumentException(msg) - } + val methodDescriptor = assembleMethodDescriptor(call) + if (map.contains(methodDescriptor)) { + map(methodDescriptor) += 1 } else { - false // default is coarse grained + map(methodDescriptor) = 1 } - - (className, isFineGrainedAnalysis) + } } override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean ): ReportableAnalysisResult = { - val (className, isFineGrainedAnalysis) = getAnalysisParameters(parameters) - val resultMap: ConcurrentHashMap[String, AtomicInteger] = new ConcurrentHashMap() - val tacProvider = project.get(LazyDetachedTACAIKey) + val resultMap = mutable.Map[String, Int]() + val tacProvider = project.get(SimpleTACAIKey) - project.parForeachMethodWithBody() { methodInfo ⇒ - tacProvider(methodInfo.method).stmts.foreach { stmt ⇒ + project.allMethodsWithBody.foreach { m ⇒ + tacProvider(m).stmts.foreach { stmt ⇒ (stmt.astID: @switch) match { - case Assignment.ASTID | ExprStmt.ASTID ⇒ - stmt.asAssignmentLike.expr match { - case c: Call[V] @unchecked ⇒ - processCall(c, resultMap, className, isFineGrainedAnalysis) - case _ ⇒ - } - case NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID | - StaticMethodCall.ASTID ⇒ - processCall(stmt.asMethodCall, resultMap, className, isFineGrainedAnalysis) + case Assignment.ASTID ⇒ stmt match { + case Assignment(_, _, c: VirtualFunctionCall[V]) ⇒ + processFunctionCall(c, resultMap) + case _ ⇒ + } + case ExprStmt.ASTID ⇒ stmt match { + case ExprStmt(_, c: VirtualFunctionCall[V]) ⇒ + processFunctionCall(c, resultMap) + case _ ⇒ + } case _ ⇒ } } } - val report = ListBuffer[String]("Result:") - // Transform to a list, sort in ascending order of occurrences, and format the information - resultMap.entrySet().stream().sorted { (value1, value2) ⇒ - value1.getValue.get().compareTo(value2.getValue.get()) - }.forEach(next ⇒ report.append(s"${next.getKey}: ${next.getValue}")) + val report = ListBuffer[String]("Results") + // Transform to a list, sort in ascending order of occurrences and format the information + report.appendAll(resultMap.toList.sortWith(_._2 < _._2).map { + case (descriptor: String, count: Int) ⇒ s"$descriptor: $count" + }) BasicReport(report) } From 6a033062e4f800759a679f627ba3419982d2a9aa Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Jan 2019 13:19:46 +0100 Subject: [PATCH 092/316] Added support for the evaluation of method parameters (pure strings as well as String{Builder, Buffer}). --- .../string_definition/TestMethods.java | 29 +++++++++++++++++++ .../LocalStringDefinitionAnalysis.scala | 11 ++++++- .../InterpretationHandler.scala | 12 +++++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 2e3291bd7b..b00ef19c01 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -822,6 +822,35 @@ public void crissCrossExample(String className) { analyzeString(sbRun.toString()); } + @StringDefinitionsCollection( + value = "examples that use a passed parameter to define strings that are analyzed", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ), + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=\\w" + ), + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=\\w\\w" + ) + }) + public void parameterRead(String stringValue, StringBuilder sbValue) { + analyzeString(stringValue); + analyzeString(sbValue.toString()); + + StringBuilder sb = new StringBuilder("value="); + System.out.println(sb.toString()); + sb.append(stringValue); + analyzeString(sb.toString()); + + sb.append(sbValue.toString()); + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevel = StringConstancyLevel.CONSTANT}, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 6449193e9b..c8c1242c01 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -79,6 +79,11 @@ class LocalStringDefinitionAnalysis( val uvar = data._1 val defSites = uvar.definedBy.toArray.sorted + // Function parameters are currently regarded as dynamic value; the following if finds read + // operations of strings (not String{Builder, Buffer}s, they will be handles further down + if (defSites.head < 0) { + return Result(data, StringConstancyProperty.lowerBound) + } val expr = stmts(defSites.head).asAssignment.expr val pathFinder: AbstractPathFinder = new DefaultPathFinder() @@ -93,6 +98,10 @@ class LocalStringDefinitionAnalysis( val initDefSites = InterpretationHandler.findDefSiteOfInit( expr.asVirtualFunctionCall, stmts ) + // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated + if (initDefSites.isEmpty) { + return Result(data, StringConstancyProperty.lowerBound) + } val paths = pathFinder.findPaths(initDefSites, cfg) val leanPaths = paths.makeLeanPath(uvar, stmts) @@ -218,7 +227,7 @@ class LocalStringDefinitionAnalysis( case ExprStmt(_, outerExpr) ⇒ if (InterpretationHandler.isStringBuilderBufferAppendCall(outerExpr)) { val param = outerExpr.asVirtualFunctionCall.params.head.asVar - param.definedBy.foreach { ds ⇒ + param.definedBy.filter(_ >= 0).foreach { ds ⇒ val expr = stmts(ds).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { foundDependees.append(( diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 23d435acdc..73421adabc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -3,6 +3,7 @@ package org.opalj.fpcf.analyses.string_definition.interpretation import org.opalj.br.cfg.CFG import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.fpcf.string_definition.properties.StringConstancyType @@ -51,7 +52,10 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { * empty list will be returned. */ def processDefSite(defSite: Int): List[StringConstancyInformation] = { - if (defSite < 0 || processedDefSites.contains(defSite)) { + // Function parameters are not evaluated but regarded as unknown + if (defSite < 0) { + return List(StringConstancyProperty.lowerBound.stringConstancyInformation) + } else if (processedDefSites.contains(defSite)) { return List() } processedDefSites.append(defSite) @@ -177,7 +181,7 @@ object InterpretationHandler { } val defSites = ListBuffer[Int]() - val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.toArray: _*) + val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.filter(_ >= 0).toArray: _*) while (stack.nonEmpty) { val next = stack.pop() stmts(next) match { @@ -186,7 +190,7 @@ object InterpretationHandler { case _: New ⇒ defSites.append(next) case vfc: VirtualFunctionCall[V] ⇒ - stack.pushAll(vfc.receiver.asVar.definedBy.toArray) + stack.pushAll(vfc.receiver.asVar.definedBy.filter(_ >= 0).toArray) } case _ ⇒ } @@ -210,7 +214,7 @@ object InterpretationHandler { stmts(ds) match { // E.g., a call to `toString` or `append` case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ - vfc.receiver.asVar.definedBy.foreach { innerDs ⇒ + vfc.receiver.asVar.definedBy.filter(_ >= 0).foreach { innerDs ⇒ stmts(innerDs) match { case Assignment(_, _, expr: New) ⇒ news.append(expr) From 83680a5f6e3dbcf56c3c75fa7055bc666aa415e8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Jan 2019 16:48:09 +0100 Subject: [PATCH 093/316] 1) isRelevantMethod was not 100 % correct; 2) Had to change how to add items to the property store (as the interface has changed) --- .../opalj/support/info/StringAnalysisReflectiveCalls.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index cab8d330e5..933893bcaa 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -78,8 +78,9 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { private def isRelevantMethod( declaringClass: ReferenceType, methodName: String, methodDescriptor: MethodDescriptor ): Boolean = - relevantMethodNames.contains(methodName) && (declaringClass.toJava == "java.lang.Class" || - methodDescriptor.returnType.toJava.contains("java.lang.reflect.")) + relevantMethodNames.contains(methodName) && (declaringClass.toJava == "java.lang.Class" && + (methodDescriptor.returnType.toJava.contains("java.lang.reflect.") || + methodDescriptor.returnType.toJava.contains("java.lang.Class"))) /** * Helper function that checks whether an array of [[Instruction]]s contains at least one @@ -109,7 +110,7 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { ): Unit = { if (isRelevantMethod(call.declaringClass, call.name, call.descriptor)) { val duvar = call.params.head.asVar - ps((List(duvar), method), StringConstancyProperty.key) match { + ps((duvar, method), StringConstancyProperty.key) match { case FinalEP(_, prop) ⇒ resultMap(call.name).append(prop.stringConstancyInformation) case _ ⇒ From 1e4cfa978a291d0e9eaab2001fab0780111db156 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 1 Jan 2019 17:00:49 +0100 Subject: [PATCH 094/316] Refined the reduceMultiple method. --- .../properties/StringConstancyInformation.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala index 5fadd85d68..88ae7626fe 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala @@ -1,15 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.string_definition.properties +import org.opalj.fpcf.properties.StringConstancyProperty + /** * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this * parameter can be omitted. * @author Patrick Mell */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, - constancyType: StringConstancyType.Value = StringConstancyType.APPEND, - possibleStrings: String = "" + constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, + constancyType: StringConstancyType.Value = StringConstancyType.APPEND, + possibleStrings: String = "" ) /** @@ -51,6 +53,10 @@ object StringConstancyInformation { */ def reduceMultiple(scis: List[StringConstancyInformation]): StringConstancyInformation = { scis.length match { + // The list may be empty, e.g., if the UVar passed to the analysis, refers to a + // VirtualFunctionCall (they are not interpreted => an empty list is returned) => return + // the corresponding information + case 0 ⇒ StringConstancyProperty.lowerBound.stringConstancyInformation case 1 ⇒ scis.head case _ ⇒ // Reduce val reduced = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( From fbc90c1ff62a028f303f7421b6f0abba2f059989 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 2 Jan 2019 17:45:20 +0100 Subject: [PATCH 095/316] Extended the analysis in the way that it can now be configured using program arguments. --- .../support/info/ClassUsageAnalysis.scala | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala index 825f325e64..c14dc764cf 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala @@ -8,6 +8,8 @@ import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.log.GlobalLogContext +import org.opalj.log.OPALLogger import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt @@ -23,13 +25,17 @@ import scala.collection.mutable.ListBuffer * analysis collect information on which methods are called on objects of that class as well as how * often. * - * Currently, the analysis can be configured by setting the two object variables - * [[ClassUsageAnalysis.className]] and [[ClassUsageAnalysis.isFineGrainedAnalysis]]. + * The analysis can be configured by passing the following optional parameters: `class` (the class + * to analyze), `granularity` (fine- or coarse-grained; defines which information will be gathered + * by an analysis run). For further information see + * [[ClassUsageAnalysis.analysisSpecificParametersDescription]]. * * @author Patrick Mell */ object ClassUsageAnalysis extends DefaultOneStepAnalysis { + implicit val logContext: GlobalLogContext.type = GlobalLogContext + override def title: String = "Class Usage Analysis" override def description: String = { @@ -41,7 +47,7 @@ object ClassUsageAnalysis extends DefaultOneStepAnalysis { * The fully-qualified name of the class that is to be analyzed in a Java format, i.e., dots as * package / class separators. */ - private val className = "java.lang.StringBuilder" + private var className = "java.lang.StringBuilder" /** * The analysis can run in two modes: Fine-grained or coarse-grained. Fine-grained means that @@ -51,7 +57,7 @@ object ClassUsageAnalysis extends DefaultOneStepAnalysis { * base object as well as the method name are equal, i.e., overloaded methods are not * distinguished. */ - private val isFineGrainedAnalysis = true + private var isFineGrainedAnalysis = false /** * Takes a [[Call]] and assembles the method descriptor for this call. The granularity is @@ -86,9 +92,52 @@ object ClassUsageAnalysis extends DefaultOneStepAnalysis { } } + override def analysisSpecificParametersDescription: String = { + "[-class= (Default: java.lang.StringBuilder)]\n"+ + "[-granularity= (Default: coarse)]" + } + + private final val parameterNameForClass = "-class=" + private final val parameterNameForGranularity = "-granularity=" + + override def checkAnalysisSpecificParameters(parameters: Seq[String]): Traversable[String] = { + val remainingParameters = + parameters.filter { p ⇒ + !p.contains(parameterNameForClass) && !p.contains(parameterNameForGranularity) + } + super.checkAnalysisSpecificParameters(remainingParameters) + } + + /** + * Takes the parameters passed as program arguments, i.e., in the format + * "-[param name]=[value]", extracts the values and sets the corresponding object variables. + */ + private def setAnalysisParameters(parameters: Seq[String]): Unit = { + val classParam = parameters.find(_.startsWith(parameterNameForClass)) + if (classParam.isDefined) { + className = classParam.get.substring(classParam.get.indexOf("=") + 1) + } + + val granularityParam = parameters.find(_.startsWith(parameterNameForGranularity)) + if (granularityParam.isDefined) { + val granularity = granularityParam.get.substring(granularityParam.get.indexOf("=") + 1) + if (granularity == "fine") { + isFineGrainedAnalysis = true + } else if (granularity == "coarse") { + isFineGrainedAnalysis = false + } else { + val errMsg = s"failed parsing the granularity; it must be either 'fine' or "+ + s"'coarse' but got '$granularity'" + OPALLogger.error("fatal", errMsg) + sys.exit(2) + } + } + } + override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean ): ReportableAnalysisResult = { + setAnalysisParameters(parameters) val resultMap = mutable.Map[String, Int]() val tacProvider = project.get(SimpleTACAIKey) From d2f4c3029d019b13ac52291457ef22ef7cb70880 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 5 Jan 2019 16:26:58 +0100 Subject: [PATCH 096/316] More refinements were necessary to handle method parameters properly. --- .../interpretation/ArrayLoadInterpreter.scala | 11 +++++++++-- .../interpretation/InterpretationHandler.scala | 2 +- .../string_definition/preprocessing/Path.scala | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala index 59dc5ee50e..e07d1e90ac 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala @@ -7,9 +7,10 @@ import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore import org.opalj.tac.Stmt import org.opalj.tac.TACStmts - import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.properties.StringConstancyProperty + /** * The `ArrayLoadInterpreter` is responsible for processing [[ArrayLoad]] expressions. * @@ -31,7 +32,8 @@ class ArrayLoadInterpreter( val stmts = cfg.code.instructions val children = ListBuffer[StringConstancyInformation]() // Loop over all possible array values - instr.arrayRef.asVar.definedBy.toArray.sorted.foreach { next ⇒ + val defSites = instr.arrayRef.asVar.definedBy.toArray + defSites.filter(_ >= 0).sorted.foreach { next ⇒ val arrDecl = stmts(next) val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted sortedArrDeclUses.filter { @@ -44,6 +46,11 @@ class ArrayLoadInterpreter( } } + // In case it refers to a method parameter, add a dynamic string property + if (defSites.exists(_ < 0)) { + children.append(StringConstancyProperty.lowerBound.stringConstancyInformation) + } + children.toList } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 73421adabc..ba60561ea9 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -210,7 +210,7 @@ object InterpretationHandler { val news = ListBuffer[New]() // HINT: It might be that the search has to be extended to further cases - duvar.definedBy.foreach { ds ⇒ + duvar.definedBy.filter(_ >= 0).foreach { ds ⇒ stmts(ds) match { // E.g., a call to `toString` or `append` case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 4c4b8b5b59..11c9bafe83 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -96,7 +96,8 @@ case class Path(elements: List[SubPath]) { stmts(popped) match { case a: Assignment[V] if a.expr.isInstanceOf[VirtualFunctionCall[V]] ⇒ - stack.pushAll(a.expr.asVirtualFunctionCall.receiver.asVar.definedBy.toArray) + val receiver = a.expr.asVirtualFunctionCall.receiver.asVar + stack.pushAll(receiver.asVar.definedBy.filter(_ >= 0).toArray) // TODO: Does the following line add too much (in some cases)??? stack.pushAll(a.targetVar.asVar.usedBy.toArray) case a: Assignment[V] if a.expr.isInstanceOf[New] ⇒ From 767ff8258667a6e925173a24fdaa570aeff87781 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 6 Jan 2019 20:04:40 +0100 Subject: [PATCH 097/316] Refined how (natural) loops are detected. --- OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 91f773de73..4d8f48b3ac 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -774,10 +774,12 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() val to = successors(from).toArray - // Check for back-edges + // Check for back-edges (exclude catch nodes here as this would detect loops where + // no actually are to.filter { next ⇒ val index = seenNodes.indexOf(next) - index > -1 && domTree.doesDominate(seenNodes(index), from) + val isCatchNode = catchNodes.exists(_.handlerPC == next) + index > -1 && !isCatchNode && domTree.doesDominate(seenNodes(index), from) }.foreach(destIndex ⇒ backedges.append((from, destIndex))) seenNodes.append(from) From a6b0af160f1d3fe8ea21b31d1fb10ec381fb76f8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 11:43:18 +0100 Subject: [PATCH 098/316] Slightly refinement on the algorithm. --- .../preprocessing/DefaultPathFinder.scala | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 3e837dd17b..e4ae8be517 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -68,6 +68,9 @@ class DefaultPathFinder extends AbstractPathFinder { var loopEndingIndex = -1 var belongsToLoopEnding = false var belongsToLoopHeader = false + // In some cases, this element will be used later on to append nested path elements of + // deeply-nested structures + var refToAppend: Option[NestedPathElement] = None // Append everything of the current basic block to the path for (i ← bb.startPC.to(bb.endPC)) { @@ -115,15 +118,17 @@ class DefaultPathFinder extends AbstractPathFinder { } // Within a nested structure => append to an inner element else { // For loops - var ref: NestedPathElement = nestedElementsRef(currSplitIndex.head) + refToAppend = Some(nestedElementsRef(currSplitIndex.head)) // Refine for conditionals and try-catch(-finally) - ref.elementType match { + refToAppend.get.elementType match { case Some(t) if t == NestedPathType.CondWithAlternative || t == NestedPathType.TryCatchFinally ⇒ - ref = ref.element(currSplitIndex.head).asInstanceOf[NestedPathElement] + refToAppend = Some(refToAppend.get.element( + currSplitIndex.head + ).asInstanceOf[NestedPathElement]) case _ ⇒ } - ref.element.append(toAppend) + refToAppend.get.element.append(toAppend) } } @@ -185,11 +190,14 @@ class DefaultPathFinder extends AbstractPathFinder { // this includes if a node has a catch node as successor if ((successorsToAdd.length > 1 && !isLoopHeader) || catchSuccessors.nonEmpty) { seenCatchNodes ++= catchSuccessors.map(n ⇒ (n.nodeId, Unit)) - val appendSite = if (numSplits.isEmpty) path else - nestedElementsRef(currSplitIndex.head).element + // Simply append to path if no nested structure is available, otherwise append to + // the correct nested path element + val appendSite = if (numSplits.isEmpty) path + else if (refToAppend.isDefined) refToAppend.get.element + else nestedElementsRef(currSplitIndex.head).element var relevantNumSuccessors = successors.size - var ifWithElse = true + var ifWithElse = true if (isCondWithoutElse(popped, cfg)) { // If there are catch node successors, the number of relevant successor equals // the number of successors (because catch node are excluded here) @@ -209,7 +217,6 @@ class DefaultPathFinder extends AbstractPathFinder { outerNested.element.reverse.foreach { next ⇒ nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) } - // nestedElementsRef.prepend(outerNested) appendSite.append(outerNested) } } From 8cfd4727dbad2dc64768cf0be68b8a7612640d4b Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 13:52:13 +0100 Subject: [PATCH 099/316] Minor refinement on the check whether an "if" has a corresponding "else" (one case, where the next supposed "else" branch followed directly after the "if", was missed). --- .../string_definition/preprocessing/AbstractPathFinder.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index c1c72ce0ee..cd24a7b1dd 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -138,7 +138,7 @@ trait AbstractPathFinder { while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() val to = from.successors - if (to.contains(cfg.bb(lastEle))) { + if (from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) { return true } seenNodes.append(from) From d50b1583ed58722e2fbe317cf6e8986bf0a710c4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 14:29:39 +0100 Subject: [PATCH 100/316] "seenNodes" contained one element too much. --- .../string_definition/preprocessing/AbstractPathFinder.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index cd24a7b1dd..94d6791bc1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -133,7 +133,7 @@ trait AbstractPathFinder { // For every successor (except the very last one), execute a DFS to check whether the very // last element is a successor. If so, this represents a path past the if (or if-elseif). branches.count { next ⇒ - val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) + val seenNodes = ListBuffer[CFGNode](cfg.bb(next)) val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() From 49ef8b4d917d0de7e6821b3c0bc85e5a3c39d2b0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 16:12:46 +0100 Subject: [PATCH 101/316] Minor change on the loop finding procedure. --- OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 4d8f48b3ac..d2ac05c47e 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -780,7 +780,17 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( val index = seenNodes.indexOf(next) val isCatchNode = catchNodes.exists(_.handlerPC == next) index > -1 && !isCatchNode && domTree.doesDominate(seenNodes(index), from) - }.foreach(destIndex ⇒ backedges.append((from, destIndex))) + }.foreach { destIndex ⇒ + // There are loops that have more than one edge leaving the loop; let x denote + // the loop header and y1, y2 two edges that leave the loop with y1 happens + // before y2; this method only saves one loop per loop header, thus y1 is + // removed as it is still implicitly contained in the loop denoted by x to y2 + // (note that this does not apply for nested loops, they are kept) + backedges.filter { + case (_: Int, oldFrom: Int) ⇒ oldFrom == destIndex + }.foreach(backedges -= _) + backedges.append((from, destIndex)) + } seenNodes.append(from) toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) From c8a954b7f3466a0839a64e09c577c7a891a5cd07 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 17:40:24 +0100 Subject: [PATCH 102/316] "seenNodes" needs to contain the branching site (removing it was not correct). --- .../string_definition/preprocessing/AbstractPathFinder.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 94d6791bc1..cd24a7b1dd 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -133,7 +133,7 @@ trait AbstractPathFinder { // For every successor (except the very last one), execute a DFS to check whether the very // last element is a successor. If so, this represents a path past the if (or if-elseif). branches.count { next ⇒ - val seenNodes = ListBuffer[CFGNode](cfg.bb(next)) + val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() From f7484f467bc4eeb4c6a23444d2507a8e842765ed Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 17:46:22 +0100 Subject: [PATCH 103/316] Path finding algorithms are now required to find all paths from the start node of the CFG (and not of a custom start node). --- .../LocalStringDefinitionAnalysis.scala | 10 ++--- .../preprocessing/AbstractPathFinder.scala | 8 ++-- .../preprocessing/DefaultPathFinder.scala | 39 +++++++++++-------- .../preprocessing/Path.scala | 19 ++++++++- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index c8c1242c01..ec0e7e4c0a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -95,14 +95,14 @@ class LocalStringDefinitionAnalysis( var state: ComputationState = null if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { - val initDefSites = InterpretationHandler.findDefSiteOfInit( - expr.asVirtualFunctionCall, stmts - ) // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated - if (initDefSites.isEmpty) { + if (InterpretationHandler.findDefSiteOfInit( + expr.asVirtualFunctionCall, stmts + ).isEmpty) { return Result(data, StringConstancyProperty.lowerBound) } - val paths = pathFinder.findPaths(initDefSites, cfg) + + val paths = pathFinder.findPaths(cfg) val leanPaths = paths.makeLeanPath(uvar, stmts) // Find DUVars, that the analysis of the current entity depends on diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index cd24a7b1dd..e8990dcb1c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -149,14 +149,12 @@ trait AbstractPathFinder { } /** - * Implementations of this function find all paths starting from the sites, given by - * `startSites`, within the provided control flow graph, `cfg`. As this is executed within the + * Implementations of this function find all paths, starting from the start node of the given + * `cfg`, within the provided control flow graph, `cfg`. As this is executed within the * context of a string definition analysis, implementations are free to decide whether they * include only statements that work on [[StringBuffer]] / [[StringBuilder]] or include all * statements in the paths. * - * @param startSites A list of possible start sites, that is, initializations. Several start - * sites denote that an object is initialized within a conditional. * @param cfg The underlying control flow graph which servers as the basis to find the paths. * @return Returns all found paths as a [[Path]] object. That means, the return object is a flat * structure, however, captures all hierarchies and (nested) flows. Note that a @@ -165,6 +163,6 @@ trait AbstractPathFinder { * implementations to attach these information to [[NestedPathElement]]s (so that * procedures using results of this function do not need to re-process). */ - def findPaths(startSites: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Path + def findPaths(cfg: CFG[Stmt[V], TACStmts[V]]): Path } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index e4ae8be517..cacde62ad2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -29,10 +29,10 @@ class DefaultPathFinder extends AbstractPathFinder { * * @see [[AbstractPathFinder.findPaths]] */ - override def findPaths(startSites: List[Int], cfg: CFG[Stmt[V], TACStmts[V]]): Path = { + override def findPaths(cfg: CFG[Stmt[V], TACStmts[V]]): Path = { // path will accumulate all paths val path = ListBuffer[SubPath]() - var stack = IntArrayStack.fromSeq(startSites.reverse) + var stack = IntArrayStack(cfg.startBlock.startPC) val seenElements = ListBuffer[Int]() // For storing the node IDs of all seen catch nodes (they are to be used only once, thus // this store) @@ -47,19 +47,6 @@ class DefaultPathFinder extends AbstractPathFinder { val nestedElementsRef = ListBuffer[NestedPathElement]() val natLoops = cfg.findNaturalLoops() - // Multiple start sites => We start within a conditional => Prepare for that - if (startSites.size > 1) { - val outerNested = - generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) - numSplits.append(startSites.size) - currSplitIndex.append(0) - outerNested.element.reverse.foreach { next ⇒ - nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) - } - nestedElementsRef.append(outerNested) - path.append(outerNested) - } - while (stack.nonEmpty) { val popped = stack.pop() val bb = cfg.bb(popped) @@ -88,10 +75,13 @@ class DefaultPathFinder extends AbstractPathFinder { numBackedgesLoop.prepend(bb.predecessors.size - 1) backedgeLoopCounter.prepend(0) + val appendLocation = if (nestedElementsRef.nonEmpty) + nestedElementsRef.head.element else path + val outer = generateNestPathElement(0, NestedPathType.Repetition) outer.element.append(toAppend) nestedElementsRef.prepend(outer) - path.append(outer) + appendLocation.append(outer) belongsToLoopHeader = true } // For loop ending, find the top-most loop from the stack and add to that element @@ -148,6 +138,9 @@ class DefaultPathFinder extends AbstractPathFinder { val hasSeenSuccessor = successors.foldLeft(false) { (old: Boolean, next: Int) ⇒ old || seenElements.contains(next) } + val hasLoopHeaderSuccessor = successors.exists( + isHeadOfLoop(_, cfg.findNaturalLoops(), cfg) + ) // Clean a loop from the stacks if the end of a loop was reached if (loopEndingIndex != -1) { @@ -163,14 +156,26 @@ class DefaultPathFinder extends AbstractPathFinder { backedgeLoopCounter.remove(0) } // For join points of branchings, do some housekeeping (explicitly excluding loops) else if (currSplitIndex.nonEmpty && + // The following condition is needed as a loop is not closed when the next statement + // still belongs to he loop, i.e., this back-edge is not the last of the loop + (!hasLoopHeaderSuccessor || isLoopEnding) && (hasSuccessorWithAtLeastNPredecessors(bb) || hasSeenSuccessor)) { if (nestedElementsRef.head.elementType.getOrElse(NestedPathType.TryCatchFinally) != NestedPathType.Repetition) { currSplitIndex(0) += 1 if (currSplitIndex.head == numSplits.head) { + nestedElementsRef.remove(0, numSplits.head) + numSplits.remove(0) + currSplitIndex.remove(0) + } + // It might be that an if(-else) but ALSO a loop needs to be closed here + val hasLoopToClose = nestedElementsRef.nonEmpty && + nestedElementsRef.head.elementType.isDefined && + nestedElementsRef.head.elementType.get == NestedPathType.Repetition + if (hasLoopToClose) { + nestedElementsRef.remove(0, 1) numSplits.remove(0) currSplitIndex.remove(0) - nestedElementsRef.remove(0) } } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 11c9bafe83..31d0ba7e39 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -126,6 +126,14 @@ case class Path(elements: List[SubPath]) { } } + /** + * Takes a [[NestedPathElement]] and removes the outermost nesting, i.e., the path contained + * in `npe` will be the path being returned. + */ + private def removeOuterBranching(npe: NestedPathElement): ListBuffer[SubPath] = { + ListBuffer[SubPath](npe.element: _*) + } + /** * Takes a [[NestedPathElement]], `npe`, and an `endSite` and strips all branches that do not * contain `endSite`. ''Stripping'' here means to clear the other branches. @@ -248,7 +256,7 @@ case class Path(elements: List[SubPath]) { case _ ⇒ true } }.map { s ⇒ (s, Unit) }.toMap - val leanPath = ListBuffer[SubPath]() + var leanPath = ListBuffer[SubPath]() val endSite = obj.definedBy.head var reachedEndSite = false @@ -274,6 +282,15 @@ case class Path(elements: List[SubPath]) { } } + // If everything is within a nested path element, ignore it (it is not relevant, as + // everything happens within that branch anyway) + if (leanPath.tail.isEmpty) { + leanPath.head match { + case npe: NestedPathElement ⇒ leanPath = removeOuterBranching(npe) + case _ ⇒ + } + } + // If the last element is a conditional, keep only the relevant branch (the other is not // necessary and stripping it simplifies further steps; explicitly exclude try-catch here!) leanPath.last match { From eb919019899669b9a9a3e12bd0ca027a40be971e Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 18:32:59 +0100 Subject: [PATCH 104/316] removeOuterBranching works now recursively. --- .../analyses/string_definition/preprocessing/Path.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 31d0ba7e39..bc9f2b1914 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -131,7 +131,14 @@ case class Path(elements: List[SubPath]) { * in `npe` will be the path being returned. */ private def removeOuterBranching(npe: NestedPathElement): ListBuffer[SubPath] = { - ListBuffer[SubPath](npe.element: _*) + if (npe.element.tail.isEmpty) { + npe.element.head match { + case innerNpe: NestedPathElement ⇒ removeOuterBranching(innerNpe) + case fpe: SubPath ⇒ ListBuffer[SubPath](fpe) + } + } else { + ListBuffer[SubPath](npe.element: _*) + } } /** From 369c228e846d6fb9a23788a524557af6333cf02c Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 19:40:59 +0100 Subject: [PATCH 105/316] Refined the makeLeanPathAcc function. --- .../preprocessing/Path.scala | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index bc9f2b1914..9c994842ca 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -194,36 +194,46 @@ case class Path(elements: List[SubPath]) { includeAlternatives: Boolean = false ): (Option[NestedPathElement], Boolean) = { val elements = ListBuffer[SubPath]() + var stop = false var hasTargetBeenSeen = false val isTryCatch = includeAlternatives || (toProcess.elementType.isDefined && toProcess.elementType.get == NestedPathType.TryCatchFinally) - toProcess.element.foreach { - case fpe: FlatPathElement if !hasTargetBeenSeen ⇒ - if (siteMap.contains(fpe.element) && !hasTargetBeenSeen) { - elements.append(fpe.copy()) - } - if (fpe.element == endSite) { - hasTargetBeenSeen = true - } - case npe: NestedPathElement if isTryCatch ⇒ - val (leanedSubPath, _) = makeLeanPathAcc( - npe, siteMap, endSite, includeAlternatives = true - ) - if (leanedSubPath.isDefined) { - elements.append(leanedSubPath.get) - } - case npe: NestedPathElement ⇒ - if (!hasTargetBeenSeen) { - val (leanedSubPath, wasTargetSeen) = makeLeanPathAcc(npe, siteMap, endSite) - if (leanedSubPath.isDefined) { - elements.append(leanedSubPath.get) - } - if (wasTargetSeen) { - hasTargetBeenSeen = true - } + toProcess.element.foreach { next ⇒ + // The stop flag is used to make sure that within a sub-path only the elements up to the + // endSite are gathered (if endSite is within this sub-path) + if (!stop) { + next match { + case fpe: FlatPathElement if !hasTargetBeenSeen ⇒ + if (siteMap.contains(fpe.element) && !hasTargetBeenSeen) { + elements.append(fpe.copy()) + } + if (fpe.element == endSite) { + hasTargetBeenSeen = true + stop = true + } + case npe: NestedPathElement if isTryCatch ⇒ + val (leanedSubPath, _) = makeLeanPathAcc( + npe, siteMap, endSite, includeAlternatives = true + ) + if (leanedSubPath.isDefined) { + elements.append(leanedSubPath.get) + } + case npe: NestedPathElement ⇒ + if (!hasTargetBeenSeen) { + val (leanedSubPath, wasTargetSeen) = makeLeanPathAcc( + npe, siteMap, endSite + ) + if (leanedSubPath.isDefined) { + elements.append(leanedSubPath.get) + } + if (wasTargetSeen) { + hasTargetBeenSeen = true + } + } + case _ ⇒ } - case _ ⇒ + } } if (elements.nonEmpty) { From bd1f3818a8b163f3234209255997824ac42763f0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 7 Jan 2019 20:12:33 +0100 Subject: [PATCH 106/316] Methods, which return a string but do not belong to a group of interpreted (such as 'append') methods, now produce "\w" instead of nothing. --- .../VirtualFunctionCallInterpreter.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 8d7cf357fc..d4692d3fa0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -7,9 +7,11 @@ import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.ObjectType import org.opalj.tac.VirtualFunctionCall /** @@ -41,8 +43,15 @@ class VirtualFunctionCallInterpreter( * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For * further information how this operation is processed, see * [[VirtualFunctionCallInterpreter.interpretReplaceCall]]. + * + *
  • + * Apart from these supported methods, a list with [[StringConstancyProperty.lowerBound]] + * will be returned in case the passed method returns a [[java.lang.String]]. + *
  • * * + * If none of the above-described cases match, an empty list will be returned. + * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T): List[StringConstancyInformation] = { @@ -50,7 +59,12 @@ class VirtualFunctionCallInterpreter( case "append" ⇒ interpretAppendCall(instr).getOrElse(List()) case "toString" ⇒ interpretToStringCall(instr) case "replace" ⇒ interpretReplaceCall(instr) - case _ ⇒ List() + case _ ⇒ + instr.descriptor.returnType match { + case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ + List(StringConstancyProperty.lowerBound.stringConstancyInformation) + case _ ⇒ List() + } } } From 2355009f48f36225b32e3e491c5f3ae0a42c9a9c Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 8 Jan 2019 20:37:15 +0100 Subject: [PATCH 107/316] Needed to add a condition in order to make sure that the correct order is maintained for nested loops. --- .../string_definition/preprocessing/DefaultPathFinder.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index cacde62ad2..6fb0d50572 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -180,7 +180,8 @@ class DefaultPathFinder extends AbstractPathFinder { } } - if ((numSplits.nonEmpty || backedgeLoopCounter.nonEmpty) && (bb.predecessors.size == 1)) { + if ((numSplits.nonEmpty || backedgeLoopCounter.nonEmpty) && + (isLoopHeader || bb.predecessors.size == 1)) { // Within a conditional, prepend in order to keep the correct order val newStack = IntArrayStack.fromSeq(stack.reverse) newStack.push(IntArrayStack.fromSeq(successorsToAdd.reverse)) From 724fdfcbe7a6e0d823318d1a2261d9e83002d5e8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 08:57:10 +0100 Subject: [PATCH 108/316] Added an explanatory comment. --- .../string_definition/LocalStringDefinitionAnalysis.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index ec0e7e4c0a..ed5ade05d7 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -84,6 +84,8 @@ class LocalStringDefinitionAnalysis( if (defSites.head < 0) { return Result(data, StringConstancyProperty.lowerBound) } + // expr is used to determine whether we deal with an object and whether the value is a + // method parameter (it is fine to use only the head for these operations) val expr = stmts(defSites.head).asAssignment.expr val pathFinder: AbstractPathFinder = new DefaultPathFinder() From 10756d376aaad2a87468051e9e2ac90e21e2160b Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 10:25:59 +0100 Subject: [PATCH 109/316] makeLeanPath 1) did not find all usages in case an object has > 1 def site and 2) under some circumstances too eagerly removed outer branches (both fixed now). --- .../preprocessing/Path.scala | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 9c994842ca..157cfa214e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -267,9 +267,11 @@ case class Path(elements: List[SubPath]) { val siteMap = getAllDefAndUseSites(obj, stmts).filter { nextSite ⇒ stmts(nextSite) match { case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - newOfObj == InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) + val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) + newOfObj == news || news.exists(newOfObj.contains) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ - newOfObj == InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) + val news = InterpretationHandler.findNewOfVar(expr.receiver.asVar, stmts) + newOfObj == news || news.exists(newOfObj.contains) case _ ⇒ true } }.map { s ⇒ (s, Unit) }.toMap @@ -299,24 +301,27 @@ case class Path(elements: List[SubPath]) { } } - // If everything is within a nested path element, ignore it (it is not relevant, as - // everything happens within that branch anyway) + // If everything is within a single branch of a nested path element, ignore it (it is not + // relevant, as everything happens within that branch anyway); for loops, remove the outer + // body in any case (as there is no alternative branch to consider) if (leanPath.tail.isEmpty) { leanPath.head match { - case npe: NestedPathElement ⇒ leanPath = removeOuterBranching(npe) - case _ ⇒ + case npe: NestedPathElement if npe.elementType.get == NestedPathType.Repetition || + npe.element.tail.isEmpty ⇒ + leanPath = removeOuterBranching(npe) + case _ ⇒ + } + } else { + // If the last element is a conditional, keep only the relevant branch (the other is not + // necessary and stripping it simplifies further steps; explicitly exclude try-catch) + leanPath.last match { + case npe: NestedPathElement if npe.elementType.isDefined && + (npe.elementType.get != NestedPathType.TryCatchFinally) ⇒ + val newLast = stripUnnecessaryBranches(npe, endSite) + leanPath.remove(leanPath.size - 1) + leanPath.append(newLast) + case _ ⇒ } - } - - // If the last element is a conditional, keep only the relevant branch (the other is not - // necessary and stripping it simplifies further steps; explicitly exclude try-catch here!) - leanPath.last match { - case npe: NestedPathElement if npe.elementType.isDefined && - (npe.elementType.get != NestedPathType.TryCatchFinally) ⇒ - val newLast = stripUnnecessaryBranches(npe, endSite) - leanPath.remove(leanPath.size - 1) - leanPath.append(newLast) - case _ ⇒ } Path(leanPath.toList) From 1cd2677bf5bf50d5121f02ca3f37a1291a250c48 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 15:20:04 +0100 Subject: [PATCH 110/316] Added support for interpreting integer values (only "IntConst" at the moment). A test case was modified accordingly. --- .../string_definition/TestMethods.java | 4 +- .../BinaryExprInterpreter.scala | 4 +- .../IntegerValueInterpreter.scala | 37 +++++++++++++++++++ .../InterpretationHandler.scala | 15 +++++--- .../VirtualFunctionCallInterpreter.scala | 10 +++-- 5 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index b00ef19c01..f67db88579 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -246,7 +246,7 @@ public void multipleOptionalAppendSites(int value) { expectedLevel = DYNAMIC, expectedStrings = "(x|[AnIntegerValue])" ), @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "([AnIntegerValue]|x)" + expectedLevel = CONSTANT, expectedStrings = "(42|x)" ) }) public void ifElseWithStringBuilderWithIntExpr() { @@ -255,7 +255,7 @@ public void ifElseWithStringBuilderWithIntExpr() { int i = new Random().nextInt(); if (i % 2 == 0) { sb1.append("x"); - sb2.append(i); + sb2.append(42); } else { sb1.append(i + 1); sb2.append("x"); diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala index 835af36fa6..d5f751f303 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala @@ -39,9 +39,9 @@ class BinaryExprInterpreter( override def interpret(instr: T): List[StringConstancyInformation] = instr.cTpe match { case ComputationalTypeInt ⇒ - List(InterpretationHandler.getStringConstancyInformationForInt) + List(InterpretationHandler.getConstancyInformationForDynamicInt) case ComputationalTypeFloat ⇒ - List(InterpretationHandler.getStringConstancyInformationForFloat) + List(InterpretationHandler.getConstancyInformationForDynamicFloat) case _ ⇒ List() } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala new file mode 100644 index 0000000000..9da9e434ec --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala @@ -0,0 +1,37 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.cfg.CFG +import org.opalj.tac.IntConst +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * The `IntegerValueInterpreter` is responsible for processing [[IntConst]]s. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class IntegerValueInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = IntConst + + /** + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + List(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + )) + +} diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index ba60561ea9..4c9ac6c071 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -1,18 +1,22 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.interpretation -import org.opalj.br.cfg.CFG +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.cfg.CFG import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr import org.opalj.tac.ExprStmt import org.opalj.tac.GetField +import org.opalj.tac.IntConst import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.NonVirtualMethodCall @@ -23,9 +27,6 @@ import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * `InterpretationHandler` is responsible for processing expressions that are relevant in order to * determine which value(s) a string read operation might have. These expressions usually come from @@ -63,6 +64,8 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ new StringConstInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: IntConst) ⇒ + new IntegerValueInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ new ArrayLoadInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: New) ⇒ @@ -237,7 +240,7 @@ object InterpretationHandler { * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], * [[StringConstancyType.APPEND]], and [[StringConstancyInformation.IntValue]]. */ - def getStringConstancyInformationForInt: StringConstancyInformation = + def getConstancyInformationForDynamicInt: StringConstancyInformation = StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, @@ -249,7 +252,7 @@ object InterpretationHandler { * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], * [[StringConstancyType.APPEND]], and [[StringConstancyInformation.IntValue]]. */ - def getStringConstancyInformationForFloat: StringConstancyInformation = + def getConstancyInformationForDynamicFloat: StringConstancyInformation = StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index d4692d3fa0..067db028f6 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -121,8 +121,9 @@ class VirtualFunctionCallInterpreter( private def valueOfAppendCall( call: VirtualFunctionCall[V] ): Option[StringConstancyInformation] = { + val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append - val defSiteParamHead = call.params.head.asVar.definedBy.head + val defSiteParamHead = param.definedBy.head var value = exprHandler.processDefSite(defSiteParamHead) // If defSiteParamHead points to a New, value will be the empty list. In that case, process // the first use site (which is the call) @@ -131,12 +132,13 @@ class VirtualFunctionCallInterpreter( cfg.code.instructions(defSiteParamHead).asAssignment.targetVar.usedBy.toArray.min ) } - call.params.head.asVar.value.computationalType match { + param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ - Some(InterpretationHandler.getStringConstancyInformationForInt) + // Was already computed above + Some(value.head) case ComputationalTypeFloat ⇒ - Some(InterpretationHandler.getStringConstancyInformationForFloat) + Some(InterpretationHandler.getConstancyInformationForDynamicFloat) // Otherwise, try to compute case _ ⇒ // It might be necessary to merge the values of the receiver and of the parameter From aca20a6fdc5e09aadf892995043cbe197f3a6e6a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 15:58:31 +0100 Subject: [PATCH 111/316] Added support for characters. --- .../string_definition/TestMethods.java | 45 +++++++++++++++++++ .../InterpretationHandler.scala | 2 + .../VirtualFunctionCallInterpreter.scala | 12 ++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index f67db88579..3f9b14fbe2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -5,6 +5,7 @@ import org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection; import java.io.IOException; +import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Random; @@ -851,6 +852,50 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "an example extracted from " + + "com.oracle.webservices.internal.api.message.BasePropertySet with two " + + "definition sites and one usage site", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "(set\\w|s\\w)" + ), + }) + public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException { + String name = getName; + String setName = name.startsWith("is") ? + "set" + name.substring(2) : + 's' + name.substring(1); + + Class clazz = Class.forName("java.lang.MyClass"); + Method setter; + try { + setter = clazz.getMethod(setName); + analyzeString(setName); + } catch (NoSuchMethodException var15) { + setter = null; + System.out.println("Error occurred"); + } + } + + @StringDefinitionsCollection( + value = "an example with an unknown character read", + stringDefinitions = { + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "\\w"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "\\w"), + }) + public void unknownCharValue() { + int charCode = new Random().nextInt(200); + char c = (char) charCode; + String s = String.valueOf(c); + analyzeString(s); + + StringBuilder sb = new StringBuilder(); + sb.append(c); + analyzeString(sb.toString()); + } + // @StringDefinitions( // value = "a case with a switch with missing breaks", // expectedLevel = StringConstancyLevel.CONSTANT}, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 4c9ac6c071..cf4227b575 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -82,6 +82,8 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { new FieldInterpreter(cfg, this).interpret(expr) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ new VirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ + new StaticFunctionCallInterpreter(cfg, this).interpret(expr) case vmc: VirtualMethodCall[V] ⇒ new VirtualMethodCallInterpreter(cfg, this).interpret(vmc) case nvmc: NonVirtualMethodCall[V] ⇒ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 067db028f6..06b3e82c0f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -135,8 +135,16 @@ class VirtualFunctionCallInterpreter( param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ - // Was already computed above - Some(value.head) + // The value was already computed above; however, we need to check whether the + // append takes an int value or a char (if it is a constant char, convert it) + if (call.descriptor.parameterType(0).isCharType && + value.head.constancyLevel == StringConstancyLevel.CONSTANT) { + Some(value.head.copy( + possibleStrings = value.head.possibleStrings.toInt.toChar.toString + )) + } else { + Some(value.head) + } case ComputationalTypeFloat ⇒ Some(InterpretationHandler.getConstancyInformationForDynamicFloat) // Otherwise, try to compute From ad89eafd84b6e34d5eada71864f133a39f94a68d Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 9 Jan 2019 19:31:19 +0100 Subject: [PATCH 112/316] No need to compute the three address code twice. --- .../string_definition/LocalStringDefinitionAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index ed5ade05d7..c2dd7117ce 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -74,8 +74,8 @@ class LocalStringDefinitionAnalysis( // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lowerBound.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) - val stmts = tacProvider(data._2).stmts val cfg = tacProvider(data._2).cfg + val stmts = cfg.code.instructions val uvar = data._1 val defSites = uvar.definedBy.toArray.sorted From 60c8502477dcfd2142082ff2822d3910170013e7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 10 Jan 2019 17:13:15 +0100 Subject: [PATCH 113/316] Changed the DefaultPathFinder to early stop when the required paths have been found. --- .../string_definition/TestMethods.java | 6 +- .../LocalStringDefinitionAnalysis.scala | 13 ++-- .../InterpretationHandler.scala | 32 ++++++--- .../preprocessing/AbstractPathFinder.scala | 58 +++++++++++++++-- .../preprocessing/DefaultPathFinder.scala | 65 ++++++++++--------- 5 files changed, 118 insertions(+), 56 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index 3f9b14fbe2..cec1c0081b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -565,7 +565,11 @@ public void tryCatchFinally(String filename) { value = "case with a try-catch-finally throwable", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + // Due to early stopping finding paths within DefaultPathFinder, the + // "EOS" can not be found for the first case (the difference to the case + // tryCatchFinally is that a second CatchNode is not present in the + // throwable case) + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w)?" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index c2dd7117ce..d0b81ffab9 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -84,9 +84,6 @@ class LocalStringDefinitionAnalysis( if (defSites.head < 0) { return Result(data, StringConstancyProperty.lowerBound) } - // expr is used to determine whether we deal with an object and whether the value is a - // method parameter (it is fine to use only the head for these operations) - val expr = stmts(defSites.head).asAssignment.expr val pathFinder: AbstractPathFinder = new DefaultPathFinder() // If not empty, this very routine can only produce an intermediate result @@ -96,15 +93,15 @@ class LocalStringDefinitionAnalysis( // initialize it with null var state: ComputationState = null - if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + val call = stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated - if (InterpretationHandler.findDefSiteOfInit( - expr.asVirtualFunctionCall, stmts - ).isEmpty) { + if (initDefSites.isEmpty) { return Result(data, StringConstancyProperty.lowerBound) } - val paths = pathFinder.findPaths(cfg) + val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head, cfg) val leanPaths = paths.makeLeanPath(uvar, stmts) // Find DUVars, that the analysis of the current entity depends on diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index cf4227b575..761489e134 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -169,17 +169,11 @@ object InterpretationHandler { } /** - * Determines the definition site of the initialization of the base object that belongs to a - * ''toString'' call. - * - * @param toString The ''toString'' call of the object for which to get the initialization def - * site for. Make sure that the object is a subclass of - * [[AbstractStringBuilder]]. - * @param stmts A list of statements which will be used to lookup which one the initialization - * is. - * @return Returns the definition sites of the base object of the call. + * Helper function for [[findDefSiteOfInit]]. */ - def findDefSiteOfInit(toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]]): List[Int] = { + private def findDefSiteOfInitAcc( + toString: VirtualFunctionCall[V], stmts: Array[Stmt[V]] + ): List[Int] = { // TODO: Check that we deal with an instance of AbstractStringBuilder if (toString.name != "toString") { return List() @@ -204,6 +198,24 @@ object InterpretationHandler { defSites.sorted.toList } + /** + * Determines the definition sites of the initializations of the base object of `duvar`. This + * function assumes that the definition sites refer to `toString` calls. + * + * @param duvar The `DUVar` to get the initializations of the base object for. + * @param stmts The search context for finding the relevant information. + * @return Returns the definition sites of the base object. + */ + def findDefSiteOfInit(duvar: V, stmts: Array[Stmt[V]]): List[Int] = { + val defSites = ListBuffer[Int]() + duvar.definedBy.foreach { ds ⇒ + defSites.appendAll( + findDefSiteOfInitAcc(stmts(ds).asAssignment.expr.asVirtualFunctionCall, stmts) + ) + } + defSites.distinct.sorted.toList + } + /** * Determines the [[New]] expressions that belongs to a given `duvar`. * diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index e8990dcb1c..4db213103f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -118,7 +118,7 @@ trait AbstractPathFinder { * other successors. If this is the case, the branching corresponds to one without an * ''else'' branch. */ - def isCondWithoutElse(branchingSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + protected def isCondWithoutElse(branchingSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { val successorBlocks = cfg.bb(branchingSite).successors // CatchNode exists => Regard it as conditional without alternative if (successorBlocks.exists(_.isInstanceOf[CatchNode])) { @@ -149,13 +149,61 @@ trait AbstractPathFinder { } /** - * Implementations of this function find all paths, starting from the start node of the given - * `cfg`, within the provided control flow graph, `cfg`. As this is executed within the + * Based on the given `cfg`, this function checks whether a path from node `from` to node `to` + * exists. If so, `true` is returned and `false otherwise`. + */ + protected def doesPathExistTo(from: Int, to: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + val stack = mutable.Stack(from) + val seenNodes = mutable.Map[Int, Unit]() + seenNodes(from) = Unit + + while (stack.nonEmpty) { + val popped = stack.pop() + cfg.bb(popped).successors.foreach { nextBlock ⇒ + // -1 is okay, as this value will not be processed (due to the flag processBlock) + var startPC, endPC = -1 + var processBlock = true + nextBlock match { + case bb: BasicBlock ⇒ + startPC = bb.startPC; endPC = bb.endPC + case cn: CatchNode ⇒ + startPC = cn.startPC; endPC = cn.endPC + case _ ⇒ processBlock = false + } + + if (processBlock) { + if (startPC >= to && endPC <= to) { + // When the `to` node was seen, immediately return + return true + } else if (!seenNodes.contains(startPC)) { + stack.push(startPC) + seenNodes(startPC) = Unit + } + } + } + } + + // When this part is reached, no path could be found + false + } + + /** + * Implementations of this function find all paths starting from the sites, given by + * `startSites`, within the provided control flow graph, `cfg`. As this is executed within the * context of a string definition analysis, implementations are free to decide whether they * include only statements that work on [[StringBuffer]] / [[StringBuilder]] or include all * statements in the paths. * - * @param cfg The underlying control flow graph which servers as the basis to find the paths. + * @param startSites A list of possible start sites, that is, initializations. Several start + * sites denote that an object is initialized within a conditional. + * Implementations may or may not use this list (however, they should indicate + * whether it is required or not). + * @param endSite An end site, that is, if the element corresponding to `endSite` is + * encountered, the finding procedure can be early stopped. Implementations + * may or may not use this list (however, they should indicate whether it is + * required or not). + * @param cfg The underlying control flow graph which servers as the basis to find the paths. + * * @return Returns all found paths as a [[Path]] object. That means, the return object is a flat * structure, however, captures all hierarchies and (nested) flows. Note that a * [[NestedPathElement]] with only one child can either refer to a loop or an ''if'' @@ -163,6 +211,6 @@ trait AbstractPathFinder { * implementations to attach these information to [[NestedPathElement]]s (so that * procedures using results of this function do not need to re-process). */ - def findPaths(cfg: CFG[Stmt[V], TACStmts[V]]): Path + def findPaths(startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Path } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 6fb0d50572..2c23b33539 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -1,16 +1,16 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing -import org.opalj.br.cfg.CatchNode +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.collection.mutable.IntArrayStack import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts +import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG import org.opalj.br.cfg.ExitNode -import org.opalj.collection.mutable.IntArrayStack - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts /** * An approach based on an a naive / intuitive traversing of the control flow graph. @@ -25,14 +25,16 @@ class DefaultPathFinder extends AbstractPathFinder { * predecessors / successors. * The paths contain all instructions, not only those that modify a [[StringBuilder]] / * [[StringBuffer]] object. - * For this implementation, `endSite` is not required, thus passing any value is fine. + * For this implementation, `startSites` as well as `endSite` are required! * * @see [[AbstractPathFinder.findPaths]] */ - override def findPaths(cfg: CFG[Stmt[V], TACStmts[V]]): Path = { + override def findPaths( + startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]] + ): Path = { // path will accumulate all paths val path = ListBuffer[SubPath]() - var stack = IntArrayStack(cfg.startBlock.startPC) + var stack = IntArrayStack.fromSeq(startSites.reverse) val seenElements = ListBuffer[Int]() // For storing the node IDs of all seen catch nodes (they are to be used only once, thus // this store) @@ -47,6 +49,19 @@ class DefaultPathFinder extends AbstractPathFinder { val nestedElementsRef = ListBuffer[NestedPathElement]() val natLoops = cfg.findNaturalLoops() + // Multiple start sites => We start within a conditional => Prepare for that + if (startSites.size > 1) { + val outerNested = + generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) + numSplits.append(startSites.size) + currSplitIndex.append(0) + outerNested.element.reverse.foreach { next ⇒ + nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) + } + nestedElementsRef.append(outerNested) + path.append(outerNested) + } + while (stack.nonEmpty) { val popped = stack.pop() val bb = cfg.bb(popped) @@ -75,13 +90,10 @@ class DefaultPathFinder extends AbstractPathFinder { numBackedgesLoop.prepend(bb.predecessors.size - 1) backedgeLoopCounter.prepend(0) - val appendLocation = if (nestedElementsRef.nonEmpty) - nestedElementsRef.head.element else path - val outer = generateNestPathElement(0, NestedPathType.Repetition) outer.element.append(toAppend) nestedElementsRef.prepend(outer) - appendLocation.append(outer) + path.append(outer) belongsToLoopHeader = true } // For loop ending, find the top-most loop from the stack and add to that element @@ -138,9 +150,6 @@ class DefaultPathFinder extends AbstractPathFinder { val hasSeenSuccessor = successors.foldLeft(false) { (old: Boolean, next: Int) ⇒ old || seenElements.contains(next) } - val hasLoopHeaderSuccessor = successors.exists( - isHeadOfLoop(_, cfg.findNaturalLoops(), cfg) - ) // Clean a loop from the stacks if the end of a loop was reached if (loopEndingIndex != -1) { @@ -156,32 +165,19 @@ class DefaultPathFinder extends AbstractPathFinder { backedgeLoopCounter.remove(0) } // For join points of branchings, do some housekeeping (explicitly excluding loops) else if (currSplitIndex.nonEmpty && - // The following condition is needed as a loop is not closed when the next statement - // still belongs to he loop, i.e., this back-edge is not the last of the loop - (!hasLoopHeaderSuccessor || isLoopEnding) && (hasSuccessorWithAtLeastNPredecessors(bb) || hasSeenSuccessor)) { if (nestedElementsRef.head.elementType.getOrElse(NestedPathType.TryCatchFinally) != NestedPathType.Repetition) { currSplitIndex(0) += 1 if (currSplitIndex.head == numSplits.head) { - nestedElementsRef.remove(0, numSplits.head) - numSplits.remove(0) - currSplitIndex.remove(0) - } - // It might be that an if(-else) but ALSO a loop needs to be closed here - val hasLoopToClose = nestedElementsRef.nonEmpty && - nestedElementsRef.head.elementType.isDefined && - nestedElementsRef.head.elementType.get == NestedPathType.Repetition - if (hasLoopToClose) { - nestedElementsRef.remove(0, 1) numSplits.remove(0) currSplitIndex.remove(0) + nestedElementsRef.remove(0) } } } - if ((numSplits.nonEmpty || backedgeLoopCounter.nonEmpty) && - (isLoopHeader || bb.predecessors.size == 1)) { + if ((numSplits.nonEmpty || backedgeLoopCounter.nonEmpty) && (bb.predecessors.size == 1)) { // Within a conditional, prepend in order to keep the correct order val newStack = IntArrayStack.fromSeq(stack.reverse) newStack.push(IntArrayStack.fromSeq(successorsToAdd.reverse)) @@ -225,6 +221,11 @@ class DefaultPathFinder extends AbstractPathFinder { } appendSite.append(outerNested) } + + // We can stop once endSite was processed and there is no more path to endSite + if (popped == endSite && !stack.map(doesPathExistTo(_, endSite, cfg)).reduce(_ || _)) { + return Path(path.toList) + } } Path(path.toList) From 6e23936dc07ac18326fe810c6819ac40647c36bd Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 14 Jan 2019 10:01:42 +0100 Subject: [PATCH 114/316] Added the current state of the runner that was used to get numbers of the usage within the JDK. --- .../info/StringAnalysisReflectiveCalls.scala | 154 +++++++++++++----- 1 file changed, 115 insertions(+), 39 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index 933893bcaa..ad64602c22 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -1,26 +1,32 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.support.info +import scala.annotation.switch + import java.net.URL +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.FPCFAnalysesManagerKey +import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.PropertyStoreKey +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.analyses.string_definition.P +import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.br.instructions.Instruction import org.opalj.br.instructions.INVOKESTATIC -import org.opalj.br.MethodDescriptor import org.opalj.br.ReferenceType import org.opalj.br.instructions.INVOKEVIRTUAL import org.opalj.br.Method -import org.opalj.fpcf.FPCFAnalysesManagerKey -import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.PropertyStoreKey -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt @@ -28,10 +34,6 @@ import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall -import scala.annotation.switch -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * Analyzes a project for calls provided by the Java Reflection API and tries to determine which * string values are / could be passed to these calls. @@ -52,13 +54,37 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] + /** + * Stores a list of pairs where the first element corresponds to the entities passed to the + * analysis and the second element corresponds to the method name in which the entity occurred, + * i.e., a value in [[relevantMethodNames]]. + */ + private val entities = ListBuffer[(P, String)]() + /** * Stores all relevant method names of the Java Reflection API, i.e., those methods from the * Reflection API that have at least one string argument and shall be considered by this - * analysis. + * analysis. The string are supposed to have the format as produced by [[buildFQMethodName]]. */ private val relevantMethodNames = List( - "forName", "getField", "getDeclaredField", "getMethod", "getDeclaredMethod" + "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", + "java.lang.Class#getField", "java.lang.Class#getDeclaredField", + "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" + ) + + /** + * A list of fully-qualified method names that are to be skipped, e.g., because they make the + * analysis crash. + */ + private val ignoreMethods = List( + // Check the found paths on this one + // "com/sun/corba/se/impl/orb/ORBImpl#setDebugFlags", + "com/oracle/webservices/internal/api/message/BasePropertySet$1#run", + "java/net/URL#getURLStreamHandler", + "java/net/URLConnection#lookupContentHandlerClassFor", + // Non rt.jar + "com/sun/javafx/property/PropertyReference#reflect", + "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform" ) override def title: String = "String Analysis for Reflective Calls" @@ -69,18 +95,22 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { } /** - * Taking the `declaringClass`, the `methodName` as well as the `methodDescriptor` into - * consideration, this function checks whether a method is relevant for this analysis. + * Using a `declaringClass` and a `methodName`, this function returns a formatted version of the + * fully-qualified method name, in the format [fully-qualified class name]#[method name] + * where the separator for the fq class names is a dot, e.g., "java.lang.Class#forName". + */ + private def buildFQMethodName(declaringClass: ReferenceType, methodName: String): String = + s"${declaringClass.toJava}#$methodName" + + /** + * Taking the `declaringClass` and the `methodName` into consideration, this function checks + * whether a method is relevant for this analysis. * * @note Internally, this method makes use of [[relevantMethodNames]]. A method can only be - * relevant if its name occurs in [[relevantMethodNames]]. + * relevant if it occurs in [[relevantMethodNames]]. */ - private def isRelevantMethod( - declaringClass: ReferenceType, methodName: String, methodDescriptor: MethodDescriptor - ): Boolean = - relevantMethodNames.contains(methodName) && (declaringClass.toJava == "java.lang.Class" && - (methodDescriptor.returnType.toJava.contains("java.lang.reflect.") || - methodDescriptor.returnType.toJava.contains("java.lang.Class"))) + private def isRelevantCall(declaringClass: ReferenceType, methodName: String): Boolean = + relevantMethodNames.contains(buildFQMethodName(declaringClass, methodName)) /** * Helper function that checks whether an array of [[Instruction]]s contains at least one @@ -90,11 +120,11 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { instructions.filter(_ != null).foldLeft(false) { (previous, nextInstr) ⇒ previous || ((nextInstr.opcode: @switch) match { case INVOKESTATIC.opcode ⇒ - val INVOKESTATIC(declClass, _, methodName, methodDescr) = nextInstr - isRelevantMethod(declClass, methodName, methodDescr) + val INVOKESTATIC(declClass, _, methodName, _) = nextInstr + isRelevantCall(declClass, methodName) case INVOKEVIRTUAL.opcode ⇒ - val INVOKEVIRTUAL(declClass, methodName, methodDescr) = nextInstr - isRelevantMethod(declClass, methodName, methodDescr) + val INVOKEVIRTUAL(declClass, methodName, _) = nextInstr + isRelevantCall(declClass, methodName) case _ ⇒ false }) } @@ -108,19 +138,66 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { private def processFunctionCall( ps: PropertyStore, method: Method, call: Call[V], resultMap: ResultMapType ): Unit = { - if (isRelevantMethod(call.declaringClass, call.name, call.descriptor)) { - val duvar = call.params.head.asVar - ps((duvar, method), StringConstancyProperty.key) match { - case FinalEP(_, prop) ⇒ - resultMap(call.name).append(prop.stringConstancyInformation) - case _ ⇒ + if (isRelevantCall(call.declaringClass, call.name)) { + val fqnMethodName = s"${method.classFile.thisType.fqn}#${method.name}" + if (!ignoreMethods.contains(fqnMethodName)) { + // println( + // s"Processing ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" + // ) + val duvar = call.params.head.asVar + val e = (duvar, method) + + ps(e, StringConstancyProperty.key) match { + case FinalEP(_, prop) ⇒ + resultMap(call.name).append(prop.stringConstancyInformation) + case _ ⇒ entities.append((e, buildFQMethodName(call.declaringClass, call.name))) + } + // Add all properties to the map; TODO: Add the following to end of the analysis + ps.waitOnPhaseCompletion() + while (entities.nonEmpty) { + val nextEntity = entities.head + ps.properties(nextEntity._1).toIndexedSeq.foreach { + case FinalEP(_, prop) ⇒ + resultMap(nextEntity._2).append( + prop.asInstanceOf[StringConstancyProperty].stringConstancyInformation + ) + case _ ⇒ + } + entities.remove(0) + } } } } + /** + * Takes a `resultMap` and transforms the information contained in that map into a + * [[BasicReport]] which will serve as the final result of the analysis. + */ + private def resultMapToReport(resultMap: ResultMapType): BasicReport = { + val report = ListBuffer[String]("Results of the Reflection Analysis:") + for ((reflectiveCall, entries) ← resultMap) { + var constantCount, partConstantCount, dynamicCount = 0 + entries.foreach { + _.constancyLevel match { + case StringConstancyLevel.CONSTANT ⇒ constantCount += 1 + case StringConstancyLevel.PARTIALLY_CONSTANT ⇒ partConstantCount += 1 + case StringConstancyLevel.DYNAMIC ⇒ dynamicCount += 1 + } + } + + report.append(s"$reflectiveCall: ${entries.length}x") + report.append(s" -> Constant: ${constantCount}x") + report.append(s" -> Partially Constant: ${partConstantCount}x") + report.append(s" -> Dynamic: ${dynamicCount}x") + } + BasicReport(report) + } + override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean ): ReportableAnalysisResult = { + val t0 = System.currentTimeMillis() + implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) project.get(FPCFAnalysesManagerKey).runAll(LazyStringDefinitionAnalysis) val tacProvider = project.get(SimpleTACAIKey) @@ -129,8 +206,6 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { val resultMap: ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]]() relevantMethodNames.foreach { resultMap(_) = ListBuffer() } - identity(propertyStore) - project.allMethodsWithBody.foreach { m ⇒ // To dramatically reduce the work of the tacProvider, quickly check if a method is // relevant at all @@ -158,9 +233,10 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { } } } - val report = ListBuffer[String]("Results:") - // TODO: Define what the report shall look like - BasicReport(report) + + val t1 = System.currentTimeMillis() + println(s"Elapsed Time: ${t1 - t0} ms") + resultMapToReport(resultMap) } } From a984721fcea52f4d0206ca59a0d2c611547907fd Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 14 Jan 2019 17:12:09 +0100 Subject: [PATCH 115/316] Added primitive support for static field read operations. --- .../interpretation/GetStaticInterpreter.scala | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala new file mode 100644 index 0000000000..365d2260d4 --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.interpretation + +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel +import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.cfg.CFG +import org.opalj.tac.GetStatic +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * The `GetStaticInterpreter` is responsible for processing [[org.opalj.tac.GetStatic]]s. Currently, + * there is only primitive support, i.e., they are not analyzed but a fixed + * [[StringConstancyInformation]] is returned. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class GetStaticInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = GetStatic + + /** + * Currently, this type is not interpreted. Thus, this function always returns a list with a + * single element consisting of [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + )) + +} \ No newline at end of file From c4931287d4313bf6b377edd36bc702456c1b2685 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 14 Jan 2019 17:39:05 +0100 Subject: [PATCH 116/316] Generalized the Array(Load)Interpreter to also support ArrayStores. --- ...terpreter.scala => ArrayInterpreter.scala} | 19 +++++++++++++++++-- .../InterpretationHandler.scala | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) rename OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/{ArrayLoadInterpreter.scala => ArrayInterpreter.scala} (72%) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayInterpreter.scala similarity index 72% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala rename to OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayInterpreter.scala index e07d1e90ac..7d6092a326 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayLoadInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayInterpreter.scala @@ -10,15 +10,17 @@ import org.opalj.tac.TACStmts import scala.collection.mutable.ListBuffer import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.tac.Assignment /** - * The `ArrayLoadInterpreter` is responsible for processing [[ArrayLoad]] expressions. + * The `ArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] + * expressions. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class ArrayLoadInterpreter( +class ArrayInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { @@ -36,6 +38,7 @@ class ArrayLoadInterpreter( defSites.filter(_ >= 0).sorted.foreach { next ⇒ val arrDecl = stmts(next) val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + // Process ArrayStores sortedArrDeclUses.filter { stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ @@ -44,6 +47,18 @@ class ArrayLoadInterpreter( children.appendAll(_) } } + // Process ArrayLoads + sortedArrDeclUses.filter { + stmts(_) match { + case Assignment(_, _, _: ArrayLoad[V]) ⇒ true + case _ ⇒ false + } + } foreach { f: Int ⇒ + val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy + defs.toArray.sorted.map { exprHandler.processDefSite }.foreach { + children.appendAll(_) + } + } } // In case it refers to a method parameter, add a dynamic string property diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala index 761489e134..96ebc04d46 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala @@ -67,7 +67,7 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { case Assignment(_, _, expr: IntConst) ⇒ new IntegerValueInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new ArrayLoadInterpreter(cfg, this).interpret(expr) + new ArrayInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: New) ⇒ new NewInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ From d89970a02b32826c39ed8918afb33f645115af6e Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 15 Jan 2019 11:14:57 +0100 Subject: [PATCH 117/316] Refined the doesPathExistTo method. --- .../preprocessing/AbstractPathFinder.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 4db213103f..2023c0557b 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -103,8 +103,8 @@ trait AbstractPathFinder { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -150,11 +150,18 @@ trait AbstractPathFinder { /** * Based on the given `cfg`, this function checks whether a path from node `from` to node `to` - * exists. If so, `true` is returned and `false otherwise`. + * exists. If so, `true` is returned and `false otherwise`. Optionally, a list of `alreadySeen` + * elements can be passed which influences which paths are to be followed (when assembling a + * path ''p'' and the next node, ''n_p'' in ''p'', is a node that was already seen, the path + * will not be continued in the direction of ''n_p'' (but in other directions that are not in + * `alreadySeen`)). */ - protected def doesPathExistTo(from: Int, to: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + protected def doesPathExistTo( + from: Int, to: Int, cfg: CFG[Stmt[V], TACStmts[V]], alreadySeen: List[Int] = List() + ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() + alreadySeen.foreach(seenNodes(_) = Unit) seenNodes(from) = Unit while (stack.nonEmpty) { From 645a88e337a169eb48f8cfbd87fa732a3af3c9cc Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 15 Jan 2019 11:15:50 +0100 Subject: [PATCH 118/316] Extended the path finding procedure to recognize when it is started within a try-catch block. --- .../fixtures/string_definition/TestMethods.java | 4 +++- .../preprocessing/DefaultPathFinder.scala | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index cec1c0081b..fcaf14f7c8 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -539,8 +539,10 @@ public void withException(String filename) { @StringDefinitionsCollection( value = "case with a try-catch-finally exception", stringDefinitions = { + // For the reason why the first expected strings differ from the other, see the + // comment in tryCatchFinallyWithThrowable @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w)?" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 2c23b33539..5e340aa2b8 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -60,6 +60,16 @@ class DefaultPathFinder extends AbstractPathFinder { } nestedElementsRef.append(outerNested) path.append(outerNested) + } else { + // Is the definition site within a try? If so, we can ignore that try block as this + // scope cannot be left anyway + val defSite = startSites.head + cfg.bb(defSite).successors.filter(_.isInstanceOf[CatchNode]).foreach { + case cn: CatchNode if cn.startPC <= defSite ⇒ + seenCatchNodes(cn.nodeId) = Unit + seenElements.append(cn.handlerPC) + case _ ⇒ + } } while (stack.nonEmpty) { @@ -223,7 +233,8 @@ class DefaultPathFinder extends AbstractPathFinder { } // We can stop once endSite was processed and there is no more path to endSite - if (popped == endSite && !stack.map(doesPathExistTo(_, endSite, cfg)).reduce(_ || _)) { + if (popped == endSite && + !stack.map(doesPathExistTo(_, endSite, cfg, seenElements.toList)).reduce(_ || _)) { return Path(path.toList) } } From dfc3e78e87bb15693eb904f3855619c94a7a41df Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 15 Jan 2019 11:22:03 +0100 Subject: [PATCH 119/316] 1) Added relevant methods for javax.crypto 2) Extended the runner in the way that it now triggers the analysis for all string arguments (and not only for the first as it was before) --- .../info/StringAnalysisReflectiveCalls.scala | 74 ++++++++++++------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index ad64602c22..4cc330a597 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -13,11 +13,11 @@ import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.PropertyStoreKey +import org.opalj.fpcf.analyses.string_definition.P import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.analyses.string_definition.P import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project @@ -67,9 +67,18 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { * analysis. The string are supposed to have the format as produced by [[buildFQMethodName]]. */ private val relevantMethodNames = List( - "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", - "java.lang.Class#getField", "java.lang.Class#getDeclaredField", - "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" + // The following is for the Java Reflection API + // "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", + // "java.lang.Class#getField", "java.lang.Class#getDeclaredField", + // "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" + // The following is for the javax.crypto API + "javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", + "javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", + "javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", + "javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", + "javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", + "javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", + "javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" ) /** @@ -141,29 +150,38 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = s"${method.classFile.thisType.fqn}#${method.name}" if (!ignoreMethods.contains(fqnMethodName)) { - // println( - // s"Processing ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" - // ) - val duvar = call.params.head.asVar - val e = (duvar, method) - - ps(e, StringConstancyProperty.key) match { - case FinalEP(_, prop) ⇒ - resultMap(call.name).append(prop.stringConstancyInformation) - case _ ⇒ entities.append((e, buildFQMethodName(call.declaringClass, call.name))) - } - // Add all properties to the map; TODO: Add the following to end of the analysis - ps.waitOnPhaseCompletion() - while (entities.nonEmpty) { - val nextEntity = entities.head - ps.properties(nextEntity._1).toIndexedSeq.foreach { - case FinalEP(_, prop) ⇒ - resultMap(nextEntity._2).append( - prop.asInstanceOf[StringConstancyProperty].stringConstancyInformation - ) - case _ ⇒ - } - entities.remove(0) + println( + s"Processing ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" + ) + // Loop through all parameters and start the analysis for those that take a string + call.descriptor.parameterTypes.zipWithIndex.foreach { + case (ft, index) ⇒ + if (ft.toJava == "java.lang.String") { + val duvar = call.params(index).asVar + val e = (duvar, method) + + ps(e, StringConstancyProperty.key) match { + case FinalEP(_, prop) ⇒ resultMap(call.name).append( + prop.stringConstancyInformation + ) + case _ ⇒ entities.append( + (e, buildFQMethodName(call.declaringClass, call.name)) + ) + } + // Add all properties to the map; TODO: Add the following to end of the analysis + ps.waitOnPhaseCompletion() + while (entities.nonEmpty) { + val nextEntity = entities.head + ps.properties(nextEntity._1).toIndexedSeq.foreach { + case FinalEP(_, prop: StringConstancyProperty) ⇒ + resultMap(nextEntity._2).append( + prop.stringConstancyInformation + ) + case _ ⇒ + } + entities.remove(0) + } + } } } } From 3df533f993879956001416da88196686278dd3a0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 17 Jan 2019 17:24:47 +0100 Subject: [PATCH 120/316] Removed a condition (some loop headers were not correctly identified). --- .../preprocessing/AbstractPathFinder.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 2023c0557b..22e346d2d0 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -61,12 +61,10 @@ trait AbstractPathFinder { // The loop header might not only consist of the very first element in 'loops'; thus, check // whether the given site is between the first site of a loop and the site of the very first - // if (again, respect structures as produces by while-true loops) + // 'if' (again, respect structures as produces by while-true loops) if (!belongsToLoopHeader) { loops.foreach { nextLoop ⇒ - // The second condition is to regard only those elements as headers which have a - // backedge - if (!belongsToLoopHeader && cfg.bb(site).asBasicBlock.predecessors.size > 1) { + if (!belongsToLoopHeader) { val start = nextLoop.head var end = start while (!cfg.code.instructions(end).isInstanceOf[If[V]]) { From 6b80436e40bca0c019e7ab61773168539a9d5956 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 17 Jan 2019 17:28:14 +0100 Subject: [PATCH 121/316] Started with the new algorithm. --- .../preprocessing/DefaultPathFinder.scala | 373 ++++++++---------- 1 file changed, 172 insertions(+), 201 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index 5e340aa2b8..c6a746af00 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -4,11 +4,12 @@ package org.opalj.fpcf.analyses.string_definition.preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.collection.mutable.IntArrayStack import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.br.cfg.CatchNode +import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG -import org.opalj.br.cfg.ExitNode +import org.opalj.br.cfg.CFGNode +import org.opalj.tac.Goto +import org.opalj.tac.If import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -20,226 +21,196 @@ import org.opalj.tac.TACStmts class DefaultPathFinder extends AbstractPathFinder { /** - * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` - * and, based on that, determines in what relation a statement / instruction is with its - * predecessors / successors. - * The paths contain all instructions, not only those that modify a [[StringBuilder]] / - * [[StringBuffer]] object. - * For this implementation, `startSites` as well as `endSite` are required! - * - * @see [[AbstractPathFinder.findPaths]] + * CSInfo stores information regarding control structures (CS) in the form: Index of the start + * statement of that CS, index of the end statement of that CS and the type. */ - override def findPaths( - startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]] - ): Path = { - // path will accumulate all paths - val path = ListBuffer[SubPath]() - var stack = IntArrayStack.fromSeq(startSites.reverse) - val seenElements = ListBuffer[Int]() - // For storing the node IDs of all seen catch nodes (they are to be used only once, thus - // this store) - val seenCatchNodes = mutable.Map[Int, Unit.type]() - // numSplits serves a queue that stores the number of possible branches (or successors) - val numSplits = ListBuffer[Int]() - // Also a queue that stores the indices of which branch of a conditional to take next - val currSplitIndex = ListBuffer[Int]() - val numBackedgesLoop = ListBuffer[Int]() - val backedgeLoopCounter = ListBuffer[Int]() - // Used to quickly find the element at which to insert a sub path - val nestedElementsRef = ListBuffer[NestedPathElement]() - val natLoops = cfg.findNaturalLoops() - - // Multiple start sites => We start within a conditional => Prepare for that - if (startSites.size > 1) { - val outerNested = - generateNestPathElement(startSites.size, NestedPathType.CondWithAlternative) - numSplits.append(startSites.size) - currSplitIndex.append(0) - outerNested.element.reverse.foreach { next ⇒ - nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) - } - nestedElementsRef.append(outerNested) - path.append(outerNested) + private type CSInfo = (Int, Int, NestedPathType.Value) + private type CFGType = CFG[Stmt[V], TACStmts[V]] + + private def determineTypeOfIf(cfg: CFGType, stmtIndex: Int): NestedPathType.Value = { + // Is the first condition enough to identify loops? + if (isHeadOfLoop(stmtIndex, cfg.findNaturalLoops(), cfg)) { + NestedPathType.Repetition + } else if (isCondWithoutElse(stmtIndex, cfg)) { + NestedPathType.CondWithoutAlternative } else { - // Is the definition site within a try? If so, we can ignore that try block as this - // scope cannot be left anyway - val defSite = startSites.head - cfg.bb(defSite).successors.filter(_.isInstanceOf[CatchNode]).foreach { - case cn: CatchNode if cn.startPC <= defSite ⇒ - seenCatchNodes(cn.nodeId) = Unit - seenElements.append(cn.handlerPC) - case _ ⇒ - } + NestedPathType.CondWithAlternative + } + } + + private def getStartAndEndIndexOfLoop(headIndex: Int, cfg: CFGType): (Int, Int) = { + var startIndex = -1 + var endIndex = -1 + val relevantLoop = cfg.findNaturalLoops().filter(_ ⇒ + isHeadOfLoop(headIndex, cfg.findNaturalLoops(), cfg)) + if (relevantLoop.nonEmpty) { + startIndex = relevantLoop.head.head + endIndex = relevantLoop.head.last } + (startIndex, endIndex) + } + + private def getStartAndEndIndexOfCondWithAlternative( + branchingSite: Int, cfg: CFGType, processedIfs: mutable.Map[Int, Unit.type] + ): (Int, Int) = { + processedIfs(branchingSite) = Unit + var endSite = -1 + val stack = mutable.Stack[Int](branchingSite) while (stack.nonEmpty) { val popped = stack.pop() - val bb = cfg.bb(popped) - val isLoopHeader = isHeadOfLoop(popped, natLoops, cfg) - var isLoopEnding = false - var loopEndingIndex = -1 - var belongsToLoopEnding = false - var belongsToLoopHeader = false - // In some cases, this element will be used later on to append nested path elements of - // deeply-nested structures - var refToAppend: Option[NestedPathElement] = None - - // Append everything of the current basic block to the path - for (i ← bb.startPC.to(bb.endPC)) { - seenElements.append(i) - val toAppend = FlatPathElement(i) - - if (!isLoopEnding) { - isLoopEnding = isEndOfLoop(cfg.bb(i).endPC, natLoops) - } - - // For loop headers, insert a new nested element (and thus, do the housekeeping) - if (!belongsToLoopHeader && isHeadOfLoop(i, natLoops, cfg)) { - numSplits.prepend(1) - currSplitIndex.prepend(0) - numBackedgesLoop.prepend(bb.predecessors.size - 1) - backedgeLoopCounter.prepend(0) - - val outer = generateNestPathElement(0, NestedPathType.Repetition) - outer.element.append(toAppend) - nestedElementsRef.prepend(outer) - path.append(outer) - - belongsToLoopHeader = true - } // For loop ending, find the top-most loop from the stack and add to that element - else if (isLoopEnding) { - val loopElement = nestedElementsRef.find { - _.elementType match { - case Some(et) ⇒ et == NestedPathType.Repetition - case _ ⇒ false - } - } - if (loopElement.isDefined) { - if (!belongsToLoopEnding) { - backedgeLoopCounter(0) += 1 - if (backedgeLoopCounter.head == numBackedgesLoop.head) { - loopEndingIndex = nestedElementsRef.indexOf(loopElement.get) - } - } - loopElement.get.element.append(toAppend) - } - belongsToLoopEnding = true - } // The instructions belonging to a loop header are stored in a flat structure - else if (!belongsToLoopHeader && (numSplits.isEmpty || bb.predecessors.size > 1)) { - path.append(toAppend) - } // Within a nested structure => append to an inner element - else { - // For loops - refToAppend = Some(nestedElementsRef(currSplitIndex.head)) - // Refine for conditionals and try-catch(-finally) - refToAppend.get.elementType match { - case Some(t) if t == NestedPathType.CondWithAlternative || - t == NestedPathType.TryCatchFinally ⇒ - refToAppend = Some(refToAppend.get.element( - currSplitIndex.head - ).asInstanceOf[NestedPathElement]) - case _ ⇒ - } - refToAppend.get.element.append(toAppend) + val nextBlock = cfg.bb(popped).successors.map { + case bb: BasicBlock ⇒ bb.startPC + // Handle Catch Nodes? + case _ ⇒ -1 + }.max + var containsIf = false + for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + processedIfs(i) = Unit + containsIf = true } } - val successors = bb.successors.filter { - case _: ExitNode ⇒ false - case _ ⇒ true - }.map { - case cn: CatchNode ⇒ cn.handlerPC - case s ⇒ s.nodeId - }.toList.sorted - val catchSuccessors = bb.successors.filter { s ⇒ - s.isInstanceOf[CatchNode] && !seenCatchNodes.contains(s.nodeId) - } - val successorsToAdd = successors.filter { next ⇒ - !seenElements.contains(next) && !stack.contains(next) - } - val hasSeenSuccessor = successors.foldLeft(false) { - (old: Boolean, next: Int) ⇒ old || seenElements.contains(next) + if (containsIf) { + stack.push(nextBlock) + } else { + // Find the goto that points after the "else" part (the assumption is that this + // goto is the very last element of the current branch + endSite = cfg.code.instructions(nextBlock - 1).asGoto.targetStmt - 1 } + } - // Clean a loop from the stacks if the end of a loop was reached - if (loopEndingIndex != -1) { - // For finding the corresponding element ref, we can use the loopEndingIndex; - // however, for numSplits and currSplitIndex we need to find the correct position in - // the array first; we do this by finding the first element with only one branch - // which will correspond to the loop - val deletePos = numSplits.indexOf(1) - numSplits.remove(deletePos) - currSplitIndex.remove(deletePos) - nestedElementsRef.remove(loopEndingIndex) - numBackedgesLoop.remove(0) - backedgeLoopCounter.remove(0) - } // For join points of branchings, do some housekeeping (explicitly excluding loops) - else if (currSplitIndex.nonEmpty && - (hasSuccessorWithAtLeastNPredecessors(bb) || hasSeenSuccessor)) { - if (nestedElementsRef.head.elementType.getOrElse(NestedPathType.TryCatchFinally) != - NestedPathType.Repetition) { - currSplitIndex(0) += 1 - if (currSplitIndex.head == numSplits.head) { - numSplits.remove(0) - currSplitIndex.remove(0) - nestedElementsRef.remove(0) - } - } - } + (branchingSite, endSite) + } - if ((numSplits.nonEmpty || backedgeLoopCounter.nonEmpty) && (bb.predecessors.size == 1)) { - // Within a conditional, prepend in order to keep the correct order - val newStack = IntArrayStack.fromSeq(stack.reverse) - newStack.push(IntArrayStack.fromSeq(successorsToAdd.reverse)) - stack = newStack - } else { - // Otherwise, append (also retain the correct order) - val newStack = IntArrayStack.fromSeq(successorsToAdd.reverse) - newStack.push(IntArrayStack.fromSeq(stack.reverse)) - stack = newStack - } - // On a split point, prepare the next (nested) element (however, not for loop headers), - // this includes if a node has a catch node as successor - if ((successorsToAdd.length > 1 && !isLoopHeader) || catchSuccessors.nonEmpty) { - seenCatchNodes ++= catchSuccessors.map(n ⇒ (n.nodeId, Unit)) - // Simply append to path if no nested structure is available, otherwise append to - // the correct nested path element - val appendSite = if (numSplits.isEmpty) path - else if (refToAppend.isDefined) refToAppend.get.element - else nestedElementsRef(currSplitIndex.head).element - var relevantNumSuccessors = successors.size - - var ifWithElse = true - if (isCondWithoutElse(popped, cfg)) { - // If there are catch node successors, the number of relevant successor equals - // the number of successors (because catch node are excluded here) - if (catchSuccessors.isEmpty) { - relevantNumSuccessors -= 1 + private def getStartAndEndIndexOfCondWithoutAlternative( + branchingSite: Int, cfg: CFGType, processedIfs: mutable.Map[Int, Unit.type] + ): (Int, Int) = { + // Find the index of very last element in the if block (here: The goto element; is it always + // present?) + val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt + var endIndex = ifTarget + do { + endIndex -= 1 + } while (cfg.code.instructions(branchingSite).isInstanceOf[Goto]) + + // It is now necessary to collect all ifs that belong to the whole if condition in the + // high-level construct + cfg.bb(ifTarget).predecessors.foreach { + case pred: BasicBlock ⇒ + for (i ← pred.startPC.to(pred.endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + processedIfs(i) = Unit } - ifWithElse = false } + // How about CatchNodes? + case cn ⇒ println(cn) + } - val outerNestedType = if (catchSuccessors.nonEmpty) NestedPathType.TryCatchFinally - else if (ifWithElse) NestedPathType.CondWithAlternative - else NestedPathType.CondWithoutAlternative - val outerNested = generateNestPathElement(relevantNumSuccessors, outerNestedType) + (branchingSite, endIndex) + } - numSplits.prepend(relevantNumSuccessors) - currSplitIndex.prepend(0) - outerNested.element.reverse.foreach { next ⇒ - nestedElementsRef.prepend(next.asInstanceOf[NestedPathElement]) + private def getTryCatchFinallyInfo(cfg: CFGType): List[CSInfo] = { + // Stores the startPC as key and the index of the end of a catch (or finally if it is + // present); a map is used for faster accesses + val tryInfo = mutable.Map[Int, Int]() + + cfg.catchNodes.foreach { cn ⇒ + if (!tryInfo.contains(cn.startPC)) { + val cnWithSameStartPC = cfg.catchNodes.filter(_.startPC == cn.startPC) + // If there is only one CatchNode for a startPC, i.e., no finally, no other catches, + // the end index can be directly derived from the successors + if (cnWithSameStartPC.tail.isEmpty) { + tryInfo(cn.startPC) = cfg.bb(cn.endPC).successors.map { + case bb: BasicBlock ⇒ bb.startPC - 1 + case _ ⇒ -1 + }.max + } // Otherwise, the largest handlerPC marks the end index + else { + tryInfo(cn.startPC) = cnWithSameStartPC.map(_.handlerPC).max } - appendSite.append(outerNested) } + } + + tryInfo.map { + case (key, value) ⇒ (key, value, NestedPathType.TryCatchFinally) + }.toList + } + + private def processBasicBlock( + cfg: CFGType, stmt: Int, processedIfs: mutable.Map[Int, Unit.type] + ): CSInfo = { + val csType = determineTypeOfIf(cfg, stmt) + val (startIndex, endIndex) = csType match { + case NestedPathType.Repetition ⇒ + processedIfs(stmt) = Unit + getStartAndEndIndexOfLoop(stmt, cfg) + case NestedPathType.CondWithoutAlternative ⇒ + getStartAndEndIndexOfCondWithoutAlternative(stmt, cfg, processedIfs) + // _ covers CondWithAlternative and TryCatchFinally, however, the latter one should + // never be present as the element referring to stmts is / should be an If + case _ ⇒ + getStartAndEndIndexOfCondWithAlternative(stmt, cfg, processedIfs) + } + (startIndex, endIndex, csType) + } - // We can stop once endSite was processed and there is no more path to endSite - if (popped == endSite && - !stack.map(doesPathExistTo(_, endSite, cfg, seenElements.toList)).reduce(_ || _)) { - return Path(path.toList) + private def findControlStructures(cfg: CFGType): List[CSInfo] = { + // foundCS stores all found control structures as a triple in the form (start, end, type) + val foundCS = ListBuffer[CSInfo]() + // For a fast loop-up which if statements have already been processed + val processedIfs = mutable.Map[Int, Unit.type]() + val startBlock = cfg.startBlock + val stack = mutable.Stack[CFGNode](startBlock) + val seenCFGNodes = mutable.Map[CFGNode, Unit.type]() + seenCFGNodes(startBlock) = Unit + + while (stack.nonEmpty) { + val next = stack.pop() + seenCFGNodes(next) = Unit + + next match { + case bb: BasicBlock ⇒ + for (i ← bb.startPC.to(bb.endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]] && + !processedIfs.contains(i)) { + foundCS.append(processBasicBlock(cfg, i, processedIfs)) + processedIfs(i) = Unit + } + } + case cn: CFGNode ⇒ + println(cn) + case _ ⇒ } + + // Add unseen successors + next.successors.filter(!seenCFGNodes.contains(_)).foreach(stack.push) } - Path(path.toList) + // Add try-catch information, sort everything in ascending order in terms of the startPC and + // return + foundCS.appendAll(getTryCatchFinallyInfo(cfg)) + foundCS.sortBy { case (start, _, _) ⇒ start }.toList + } + + /** + * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` + * and, based on that, determines in what relation a statement / instruction is with its + * predecessors / successors. + * The paths contain all instructions, not only those that modify a [[StringBuilder]] / + * [[StringBuffer]] object. + * For this implementation, `startSites` as well as `endSite` are required! + * + * @see [[AbstractPathFinder.findPaths]] + */ + override def findPaths(startSites: List[Int], endSite: Int, cfg: CFGType): Path = { + val startPC = cfg.startBlock.startPC + identity(startPC) + val csInfo = findControlStructures(cfg) + identity(csInfo) + + Path(List(FlatPathElement(0), FlatPathElement(1))) } } From 8752c4e60df22ae58499ec8ff2b19a33e44fcddf Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 21 Jan 2019 20:22:16 +0100 Subject: [PATCH 122/316] Finished re-writing the path finding procedure. --- .../string_definition/TestMethods.java | 61 +- .../LocalStringDefinitionAnalysis.scala | 4 +- .../preprocessing/AbstractPathFinder.scala | 780 +++++++++++++++++- .../preprocessing/DefaultPathFinder.scala | 276 +++---- .../preprocessing/Path.scala | 28 +- 5 files changed, 943 insertions(+), 206 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java index fcaf14f7c8..06b659bed8 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java @@ -5,7 +5,9 @@ import org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection; import java.io.IOException; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Random; @@ -539,10 +541,8 @@ public void withException(String filename) { @StringDefinitionsCollection( value = "case with a try-catch-finally exception", stringDefinitions = { - // For the reason why the first expected strings differ from the other, see the - // comment in tryCatchFinallyWithThrowable @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w)?" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" @@ -571,7 +571,7 @@ public void tryCatchFinally(String filename) { // "EOS" can not be found for the first case (the difference to the case // tryCatchFinally is that a second CatchNode is not present in the // throwable case) - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w)?" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" @@ -665,7 +665,9 @@ public void replaceExamples(int value) { value = "loops that use breaks and continues (or both)", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "abc((d)?)*" + // The bytecode produces an "if" within an "if" inside the first loop, + // => two conds + expectedLevel = CONSTANT, expectedStrings = "abc(((d)?)?)*" ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "" @@ -885,6 +887,55 @@ public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException } } + @StringDefinitionsCollection( + value = "Some comprehensive example for experimental purposes taken from the JDK and " + + "slightly modified", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "Hello: (\\w|\\w|\\w)?" + ), + }) + protected void setDebugFlags(String[] var1) { + for(int var2 = 0; var2 < var1.length; ++var2) { + String var3 = var1[var2]; + + int randomValue = new Random().nextInt(); + StringBuilder sb = new StringBuilder("Hello: "); + if (randomValue % 2 == 0) { + sb.append(getRuntimeClassName()); + } else if (randomValue % 3 == 0) { + sb.append(getStringBuilderClassName()); + } else if (randomValue % 4 == 0) { + sb.append(getSimpleStringBuilderClassName()); + } + + try { + Field var4 = this.getClass().getField(var3 + "DebugFlag"); + int var5 = var4.getModifiers(); + if (Modifier.isPublic(var5) && !Modifier.isStatic(var5) && + var4.getType() == Boolean.TYPE) { + var4.setBoolean(this, true); + } + } catch (IndexOutOfBoundsException var90) { + System.out.println("Should never happen!"); + } catch (Exception var6) { + int i = 10; + i += new Random().nextInt(); + System.out.println("Some severe error occurred!" + i); + } finally { + int i = 10; + i += new Random().nextInt(); + // TODO: Control structures in finally blocks are not handles correctly + // if (i % 2 == 0) { + // System.out.println("Ready to analyze now in any case!" + i); + // } + } + + analyzeString(sb.toString()); + } + } + @StringDefinitionsCollection( value = "an example with an unknown character read", stringDefinitions = { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index d0b81ffab9..6996a61cdc 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -84,7 +84,7 @@ class LocalStringDefinitionAnalysis( if (defSites.head < 0) { return Result(data, StringConstancyProperty.lowerBound) } - val pathFinder: AbstractPathFinder = new DefaultPathFinder() + val pathFinder: AbstractPathFinder = new DefaultPathFinder(cfg) // If not empty, this very routine can only produce an intermediate result val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() @@ -101,7 +101,7 @@ class LocalStringDefinitionAnalysis( return Result(data, StringConstancyProperty.lowerBound) } - val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head, cfg) + val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) val leanPaths = paths.makeLeanPath(uvar, stmts) // Find DUVars, that the analysis of the current entity depends on diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 22e346d2d0..244b050de1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -9,17 +9,500 @@ import org.opalj.fpcf.analyses.string_definition.V import org.opalj.tac.If import org.opalj.tac.Stmt import org.opalj.tac.TACStmts - import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.tac.Switch + /** * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the * scope of string definition analyses. * + * @param cfg The control flow graph (CFG) on which instance of this class will operate on. + * * @author Patrick Mell */ -trait AbstractPathFinder { +abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { + + /** + * CSInfo stores information regarding control structures (CS) in the form: Index of the start + * statement of that CS, index of the end statement of that CS and the type. + */ + protected type CSInfo = (Int, Int, NestedPathType.Value) + + /** + * Represents control structures in a hierarchical order. The top-most level of the hierarchy + * has no [[CSInfo]], thus value can be set to `None`; all other elements are required to have + * that value set! + * + * @param hierarchy A list of pairs where the first element represents the parent and the second + * the list of children. As the list of children is of type + * [[HierarchicalCSOrder]], too, this creates a recursive structure. + * If two elements, ''e1'' and ''e2'', are on the same hierarchy level neither + * ''e1'' is a parent or child of ''e'' and nor is ''e2'' a parent or child of + * ''e1''. + */ + protected case class HierarchicalCSOrder( + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + ) + + /** + * Determines the bounds of a conditional with alternative (like an `if-else` or a `switch` with + * a `default` case, that is the indices of the first and the last statement belonging to the + * whole block (i.e., for an `if-else` this function returns the index of the very first + * statement of the `if`, including the branching site, as the first value and the index of the + * very last element of the `else` part as the second value). + * + * @param branchingSite The `branchingSite` is supposed to point at the very first `if` of the + * conditional. + * @param processedIfs A map which will be filled with the `if` statements that will be + * encountered during the processing. This might be relevant for a method + * processing all `if`s - the `if` of an `else-if` is shall probably be + * processed only once. This map can be used for that purpose. + * @return Returns the index of the start statement and the index of the end statement of the + * whole conditional as described above. + */ + private def getStartAndEndIndexOfCondWithAlternative( + branchingSite: Int, processedIfs: mutable.Map[Int, Unit.type] + ): (Int, Int) = { + processedIfs(branchingSite) = Unit + + var endSite = -1 + val stack = mutable.Stack[Int](branchingSite) + while (stack.nonEmpty) { + val popped = stack.pop() + val nextBlock = cfg.bb(popped).successors.map { + case bb: BasicBlock ⇒ bb.startPC + // Handle Catch Nodes? + case _ ⇒ -1 + }.max + var containsIf = false + for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + processedIfs(i) = Unit + containsIf = true + } + } + + if (containsIf) { + stack.push(nextBlock) + } else { + // Find the goto that points after the "else" part (the assumption is that this + // goto is the very last element of the current branch + endSite = cfg.code.instructions(nextBlock - 1).asGoto.targetStmt - 1 + } + } + + (branchingSite, endSite) + } + + /** + * Determines the bounds of a conditional without alternative (like an `if-else-if` or a + * `switch` without a `default` case, that is the indices of the first and the last statement + * belonging to the whole block (i.e., for an `if-else-if` this function returns the index of + * the very first statement of the `if`, including the branching site, as the first value and + * the index of the very last element of the `else if` part as the second value). + * + * @param branchingSite The `branchingSite` is supposed to point at the very first `if` of the + * conditional. + * @param processedIfs A map which will be filled with the `if` statements that will be + * encountered during the processing. This might be relevant for a method + * processing all `if`s - the `if` of an `else-if` is shall probably be + * processed only once. This map can be used for that purpose. + * @return Returns the index of the start statement and the index of the end statement of the + * whole conditional as described above. + */ + private def getStartAndEndIndexOfCondWithoutAlternative( + branchingSite: Int, processedIfs: mutable.Map[Int, Unit.type] + ): (Int, Int) = { + // Find the index of very last element in the if block (here: The goto element; is it always + // present?) + val nextPossibleIfBlock = cfg.bb(branchingSite).successors.map { + case bb: BasicBlock ⇒ bb.startPC + // Handle Catch Nodes? + case _ ⇒ -1 + }.max + + var nextIfIndex = -1 + for (i ← cfg.bb(nextPossibleIfBlock).startPC.to(cfg.bb(nextPossibleIfBlock).endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + processedIfs(i) = Unit + nextIfIndex = i + } + } + + var endIndex = nextPossibleIfBlock - 1 + if (nextIfIndex > -1) { + val (_, newEndIndex) = getStartAndEndIndexOfCondWithoutAlternative( + nextIfIndex, processedIfs + ) + endIndex = newEndIndex + } + + val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt + // It might be that the "i"f is the very last element in a loop; in this case, it is a + // little bit more complicated to find the end of the "if": Go up to the element that points + // to the if target element + if (ifTarget < branchingSite) { + val toVisit = mutable.Stack[Int](branchingSite) + while (toVisit.nonEmpty) { + val popped = toVisit.pop() + val successors = cfg.bb(popped).successors + if (successors.size == 1 && successors.head.asBasicBlock.startPC == ifTarget) { + endIndex = cfg.bb(popped).endPC + toVisit.clear() + } else { + toVisit.pushAll(successors.filter { + case bb: BasicBlock ⇒ bb.nodeId != ifTarget + case _ ⇒ false + }.map(_.asBasicBlock.startPC)) + } + } + } + + // It might be that this conditional is within a try block. In that case, endIndex will + // point after all catch clauses which is to much => narrow down to try block + val inTryBlocks = cfg.catchNodes.filter { cn ⇒ + branchingSite >= cn.startPC && branchingSite <= cn.endPC + } + if (inTryBlocks.nonEmpty) { + endIndex = inTryBlocks.minBy(-_.startPC).endPC + } + + // It is now necessary to collect all ifs that belong to the whole if condition (in the + // high-level construct) + cfg.bb(ifTarget).predecessors.foreach { + case pred: BasicBlock ⇒ + for (i ← pred.startPC.to(pred.endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + processedIfs(i) = Unit + } + } + // How about CatchNodes? + case _ ⇒ + } + + (branchingSite, endIndex) + } + + /** + * This function detects all `try-catch` blocks in the given CFG, extracts the indices of the + * first statement for each `try` as the as well as the indices of the last statements of the + * `try-catch` blocks and returns these pairs (along with [[NestedPathType.TryCatchFinally]]. + * + * @return Returns information on all `try-catch` blocks present in the given `cfg`. + * + * @note The bounds, which are determined by this function do not include the `finally` part of + * `try` blocks (but for the `catch` blocks). Thus, a function processing the result of + * this function can either add the `finally` to the `try` block (and keep it in the + * `catch` block(s)) or add it after the whole `try-catch` but disregards it for all + * `catch` blocks. + * @note This function has basic support for `throwable`s. + */ + private def determineTryCatchBounds(): List[CSInfo] = { + // Stores the startPC as key and the index of the end of a catch (or finally if it is + // present); a map is used for faster accesses + val tryInfo = mutable.Map[Int, Int]() + + cfg.catchNodes.foreach { cn ⇒ + if (!tryInfo.contains(cn.startPC)) { + val cnSameStartPC = cfg.catchNodes.filter(_.startPC == cn.startPC) + val hasCatchFinally = cnSameStartPC.exists(_.catchType.isEmpty) + val hasOnlyFinally = cnSameStartPC.size == 1 && hasCatchFinally + val isThrowable = cn.catchType.isDefined && + cn.catchType.get.fqn == "java/lang/Throwable" + // When there is a throwable involved, it might be the case that there is only one + // element in cnSameStartPC, the finally part; do not process it now (but in another + // catch node) + if (!hasOnlyFinally) { + if (isThrowable) { + val throwFinally = cfg.catchNodes.find(_.startPC == cn.handlerPC) + val endIndex = if (throwFinally.isDefined) throwFinally.get.endPC - 1 else + cn.endPC - 1 + tryInfo(cn.startPC) = endIndex + } // If there is only one CatchNode for a startPC, i.e., no finally, no other + // catches, the end index can be directly derived from the successors + else if (cnSameStartPC.tail.isEmpty && !isThrowable) { + tryInfo(cn.startPC) = cfg.bb(cn.endPC).successors.map { + case bb: BasicBlock ⇒ bb.startPC - 1 + case _ ⇒ -1 + }.max + } // Otherwise, the index after the try and all catches marks the end index (-1 + // to not already get the start index of the successor) + else { + if (hasCatchFinally) { + // Find out, how many elements the finally block has and adjust the try + // block accordingly + val startFinally = cnSameStartPC.map(_.handlerPC).max + val endFinally = + cfg.code.instructions(startFinally - 1).asGoto.targetStmt + val numElementsFinally = endFinally - startFinally - 1 + val endOfFinally = cnSameStartPC.map(_.handlerPC).max + tryInfo(cn.startPC) = endOfFinally - 1 - numElementsFinally + } else { + tryInfo(cn.startPC) = cfg.bb(cnSameStartPC.head.endPC).successors.map { + case bb: BasicBlock ⇒ bb.startPC + case _ ⇒ -1 + }.max - 1 + } + } + } + } + } + + tryInfo.map { + case (key, value) ⇒ (key, value, NestedPathType.TryCatchFinally) + }.toList + } + + /** + * This function serves as a helper / accumulator function that builds the recursive hierarchy + * for a given element. + * + * @param element The element for which a hierarchy is to be built. + * @param children Maps from parent elements ([[CSInfo]]) to its children. `children` is + * supposed to contain all known parent-children relations in order to guarantee + * that the recursive calls will produce a correct result as well). + * @return The hierarchical structure for `element`. + */ + private def buildHierarchy( + element: CSInfo, + children: mutable.Map[CSInfo, ListBuffer[CSInfo]] + ): HierarchicalCSOrder = { + if (!children.contains(element)) { + // Recursion anchor (no children available + HierarchicalCSOrder(List((Some(element), List()))) + } else { + HierarchicalCSOrder(List(( + Some(element), children(element).map { buildHierarchy(_, children) }.toList + ))) + } + } + + /** + * This function builds a [[Path]] that consists of a single [[NestedPathElement]] of type + * [[NestedPathType.Repetition]]. If `fill` is set to `true`, the nested path element will be + * filled with [[FlatPathElement]] ranging from `start` to `end` (otherwise, the nested path + * element remains empty and is to be filled outside this method). + * This method returns the [[Path]] element along with a list of a single element that consists + * of the tuple `(start, end)`. + */ + private def buildRepetitionPath( + start: Int, end: Int, fill: Boolean + ): (Path, List[(Int, Int)]) = { + val path = ListBuffer[SubPath]() + if (fill) { + start.to(end).foreach(i ⇒ path.append(FlatPathElement(i))) + } + (Path(List(NestedPathElement(path, Some(NestedPathType.Repetition)))), List((start, end))) + } + + /** + * This function builds the [[Path]] element for conditionals with and without alternatives + * (e.g., `if`s that have an `else` block or not); which one is determined by `pathType`. + * `start` and `end` determine the start and end index of the conditional (`start` is supposed + * to contain the initial branching site of the conditionals). + * This function determines all `if`, `else-if`, and `else` blocks and adds them to the path + * element that will be returned. If `fill` is set to `true`, the different parts will be filled + * with [[FlatPathElement]]s. + * For example, assume an `if-else` where the `if` start at index 5, ends at index 10, and the + * `else` part starts at index 11 and ends at index 20. [[Path]] will then contain a + * [[NestedPathElement]] of type [[NestedPathType.CondWithAlternative]] with two children. If + * `fill` equals `true`, the first inner path will contain flat path elements from 5 to 10 and + * the second from 11 to 20. + */ + private def buildCondPath( + start: Int, end: Int, pathType: NestedPathType.Value, fill: Boolean + ): (Path, List[(Int, Int)]) = { + // Stores the start and end indices of the parts that form the if-(else-if)*-else, i.e., if + // there is an if-else construct, startEndPairs contains two elements: 1) The start index of + // the if, the end index of the if part and 2) the start index of the else part and the end + // index of the else part + val startEndPairs = ListBuffer[(Int, Int)]() + + var endSite = -1 + val stack = mutable.Stack[Int](start) + while (stack.nonEmpty) { + val popped = stack.pop() + val nextBlock = cfg.bb(popped).successors.map { + case bb: BasicBlock ⇒ bb.startPC + // Handle Catch Nodes? + case _ ⇒ -1 + }.max + var containsIf = false + for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + containsIf = true + } + } + + if (containsIf) { + startEndPairs.append((popped, nextBlock - 1)) + stack.push(nextBlock) + } else { + endSite = nextBlock - 1 + if (endSite == start) { + endSite = end + } // The following is necessary to not exceed bounds (might be the case within a + // try block for example) + else if (endSite > end) { + endSite = end + } + startEndPairs.append((popped, endSite)) + } + } + + // Append the "else" branch (if present) + if (pathType == NestedPathType.CondWithAlternative) { + startEndPairs.append((startEndPairs.last._2 + 1, end)) + } + + val subPaths = ListBuffer[SubPath]() + startEndPairs.foreach { + case (startSubpath, endSubpath) ⇒ + val subpathElements = ListBuffer[SubPath]() + subPaths.append(NestedPathElement(subpathElements, None)) + if (fill) { + subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) + } + } + (Path(List(NestedPathElement(subPaths, Some(pathType)))), startEndPairs.toList) + } + + /** + * This function works analogously to [[buildCondPath]] only that it processes [[Switch]] + * statements and that it determines itself whether the switch contains a default case or not. + */ + private def buildPathForSwitch( + start: Int, end: Int, fill: Boolean + ): (Path, List[(Int, Int)]) = { + val startEndPairs = ListBuffer[(Int, Int)]() + val switch = cfg.code.instructions(start).asSwitch + val caseStmts = switch.caseStmts.sorted + + val containsDefault = caseStmts.length == caseStmts.distinct.length + val pathType = if (containsDefault) NestedPathType.CondWithAlternative else + NestedPathType.CondWithoutAlternative + + var previousStart = caseStmts.head + caseStmts.tail.foreach { nextStart ⇒ + val currentEnd = nextStart - 1 + if (currentEnd >= previousStart) { + startEndPairs.append((previousStart, currentEnd)) + } + previousStart = nextStart + } + if (previousStart <= end) { + startEndPairs.append((previousStart, end)) + } + + val subPaths = ListBuffer[SubPath]() + startEndPairs.foreach { + case (startSubpath, endSubpath) ⇒ + val subpathElements = ListBuffer[SubPath]() + subPaths.append(NestedPathElement(subpathElements, None)) + if (fill) { + subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) + } + } + (Path(List(NestedPathElement(subPaths, Some(pathType)))), startEndPairs.toList) + } + + /** + * This function works analogously to [[buildCondPath]], i.e., it determines the start and end + * index of the `catch` block and the start and end indices of the `catch` blocks (if present). + * + * @note Note that the built path has the following properties: The end index for the `try` + * block excludes the `finally` part if it is present; the same applies to the `catch` + * blocks! However, the `finally` block is inserted after the [[NestedPathElement]], i.e., + * the path produced by this function contains more than one element (if a `finally` + * block is present; this is handled by this function as well). + * + * @note This function has basic / primitive support for `throwable`s. + */ + private def buildTryCatchPath( + start: Int, end: Int, fill: Boolean + ): (Path, List[(Int, Int)]) = { + // For a description, see the comment of this variable in buildCondPath + val startEndPairs = ListBuffer[(Int, Int)]() + + var catchBlockStartPCs = ListBuffer[Int]() + var hasFinallyBlock = false + var throwableElement: Option[CatchNode] = None + cfg.bb(start).successors.foreach { + case cn: CatchNode ⇒ + // Add once for the try block + if (startEndPairs.isEmpty) { + startEndPairs.append((cn.startPC, cn.endPC)) + } + if (cn.catchType.isDefined && cn.catchType.get.fqn == "java/lang/Throwable") { + throwableElement = Some(cn) + } else { + catchBlockStartPCs.append(cn.handlerPC) + if (cn.catchType.isEmpty) { + hasFinallyBlock = true + } + } + case _ ⇒ + } + + if (throwableElement.isDefined) { + val throwCatch = cfg.catchNodes.find(_.startPC == throwableElement.get.handlerPC) + if (throwCatch.isDefined) { + // This is for the catch block + startEndPairs.append((throwCatch.get.startPC, throwCatch.get.endPC - 1)) + } + } else { + var numElementsFinally = 0 + if (hasFinallyBlock) { + // Find out, how many elements the finally block has + val startFinally = catchBlockStartPCs.max + val endFinally = cfg.code.instructions(startFinally - 1).asGoto.targetStmt + // -1 for unified processing further down below (because in + // catchBlockStartPCs.foreach, 1 is subtracted) + numElementsFinally = endFinally - startFinally - 1 + } else { + val endOfAfterLastCatch = cfg.bb(startEndPairs.head._2).successors.map { + case bb: BasicBlock ⇒ bb.startPC + case _ ⇒ -1 + }.max + catchBlockStartPCs.append(endOfAfterLastCatch) + } + + catchBlockStartPCs = catchBlockStartPCs.sorted + catchBlockStartPCs.zipWithIndex.foreach { + case (nextStart, i) ⇒ + if (i + 1 < catchBlockStartPCs.length) { + startEndPairs.append( + (nextStart, catchBlockStartPCs(i + 1) - 1 - numElementsFinally) + ) + } + } + } + + val subPaths = ListBuffer[SubPath]() + startEndPairs.foreach { + case (startSubpath, endSubpath) ⇒ + val subpathElements = ListBuffer[SubPath]() + subPaths.append(NestedPathElement(subpathElements, None)) + if (fill) { + subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) + } + } + + // If there is a finally part, append everything after the end of the try block up to the + // very first catch block + if (hasFinallyBlock && fill) { + subPaths.appendAll((startEndPairs.head._2 + 1).until(startEndPairs(1)._1).map { i ⇒ + FlatPathElement(i) + }) + } + + ( + Path(List(NestedPathElement(subPaths, Some(NestedPathType.TryCatchFinally)))), + startEndPairs.toList + ) + } /** * Generates a new [[NestedPathElement]] with a given number of inner [[NestedPathElement]]s. @@ -101,8 +584,8 @@ trait AbstractPathFinder { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -124,26 +607,52 @@ trait AbstractPathFinder { } val successors = successorBlocks.map(_.nodeId).toArray.sorted + + // In case, there is only one larger successor, this will be a condition without else + // (smaller indices might arise, e.g., when an "if" is the last part of a loop) + if (successors.count(_ > branchingSite) == 1) { + return true + } + // Separate the last element from all previous ones val branches = successors.reverse.tail.reverse val lastEle = successors.last - // For every successor (except the very last one), execute a DFS to check whether the very - // last element is a successor. If so, this represents a path past the if (or if-elseif). - branches.count { next ⇒ - val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) - val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) - while (toVisitStack.nonEmpty) { - val from = toVisitStack.pop() - val to = from.successors - if (from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) { - return true + val indexIf = cfg.bb(lastEle) match { + case bb: BasicBlock ⇒ + val ifPos = bb.startPC.to(bb.endPC).filter( + cfg.code.instructions(_).isInstanceOf[If[V]] + ) + if (ifPos.nonEmpty) { + ifPos.head + } else { + -1 } - seenNodes.append(from) - toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) - } - return false - } > 1 + case _ ⇒ -1 + } + + if (indexIf != -1) { + // For else-if constructs + isCondWithoutElse(indexIf, cfg) + } else { + // For every successor (except the very last one), execute a DFS to check whether the + // very last element is a successor. If so, this represents a path past the if (or + // if-elseif). + branches.count { next ⇒ + val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) + val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) + while (toVisitStack.nonEmpty) { + val from = toVisitStack.pop() + val to = from.successors + if (from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) { + return true + } + seenNodes.append(from) + toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) + } + return false + } > 1 + } } /** @@ -159,7 +668,7 @@ trait AbstractPathFinder { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_) = Unit) + alreadySeen.foreach(seenNodes(_)= Unit) seenNodes(from) = Unit while (stack.nonEmpty) { @@ -192,6 +701,233 @@ trait AbstractPathFinder { false } + /** + * Determines the bounds of a loop, that is the indices of the first and the last statement. + * + * @param index The index of the statement that is the `if` statement of the loop. This function + * can deal with `if`s within the loop header or loop footer. + * @return Returns the index of the very first statement of the loop as well as the index of the + * very last statement index. + */ + private def getStartAndEndIndexOfLoop(index: Int): (Int, Int) = { + var startIndex = -1 + var endIndex = -1 + val relevantLoop = cfg.findNaturalLoops().filter(nextLoop ⇒ + // The given index might belong either to the start or to the end of a loop + isHeadOfLoop(index, List(nextLoop), cfg) || isEndOfLoop(index, List(nextLoop))) + if (relevantLoop.nonEmpty) { + startIndex = relevantLoop.head.head + endIndex = relevantLoop.head.last + } + (startIndex, endIndex) + } + + /** + * This function determines the type of the [[If]] statement, i.e., an element of + * [[NestedPathType]] as well as the indices of the very first and very last statement that + * belong to the `if`. + * + * @param stmt The index of the statement to process. This statement must be of type [[If]]. + * @param processedIfs A map that serves as a look-up table to 1) determine which `if`s have + * already been processed (and thus will not be processed again), and 2) to + * extend this table by the `if`s encountered in this procedure. + * @return Returns the start index, end index, and type of the `if` in that order. + * + * @note For further details, see [[getStartAndEndIndexOfCondWithAlternative]], + * [[getStartAndEndIndexOfCondWithoutAlternative]], and [[determineTryCatchBounds]]. + */ + protected def processIf( + stmt: Int, processedIfs: mutable.Map[Int, Unit.type] + ): CSInfo = { + val csType = determineTypeOfIf(stmt) + val (startIndex, endIndex) = csType match { + case NestedPathType.Repetition ⇒ + processedIfs(stmt) = Unit + getStartAndEndIndexOfLoop(stmt) + case NestedPathType.CondWithoutAlternative ⇒ + getStartAndEndIndexOfCondWithoutAlternative(stmt, processedIfs) + // _ covers CondWithAlternative and TryCatchFinally, however, the latter one should + // never be present as the element referring to stmts is / should be an If + case _ ⇒ + getStartAndEndIndexOfCondWithAlternative(stmt, processedIfs) + } + (startIndex, endIndex, csType) + } + + /** + * This function determines the indices of the very first and very last statement that belong to + * the `switch` statement as well as the type of the `switch` ( + * [[NestedPathType.CondWithAlternative]] if the `switch` has a `default` case and + * [[NestedPathType.CondWithoutAlternative]] otherwise. + * + * @param stmt The index of the statement to process. This statement must be of type [[Switch]]. + * + * @return Returns the start index, end index, and type of the `switch` in that order. + */ + protected def processSwitch(stmt: Int): CSInfo = { + val switch = cfg.code.instructions(stmt).asSwitch + val caseStmts = switch.caseStmts.sorted + // TODO: How about only one case? + val end = cfg.code.instructions(caseStmts.tail.head - 1).asGoto.targetStmt - 1 + + val containsDefault = caseStmts.length == caseStmts.distinct.length + val pathType = if (containsDefault) NestedPathType.CondWithAlternative else + NestedPathType.CondWithoutAlternative + + (stmt, end, pathType) + } + + /** + * @param stmtIndex The index of the instruction that is an [[If]] and for which the type is to + * be determined. + * @return Returns a value in [[NestedPathType.Value]] except + * [[NestedPathType.TryCatchFinally]] (as their construction does not involve an [[If]] + * statement). + */ + protected def determineTypeOfIf(stmtIndex: Int): NestedPathType.Value = { + // Is the first condition enough to identify loops? + val loops = cfg.findNaturalLoops() + // The if might belong to the head or end of the loop + if (isHeadOfLoop(stmtIndex, loops, cfg) || isEndOfLoop(stmtIndex, loops)) { + NestedPathType.Repetition + } else if (isCondWithoutElse(stmtIndex, cfg)) { + NestedPathType.CondWithoutAlternative + } else { + NestedPathType.CondWithAlternative + } + } + + /** + * Finds all control structures within [[cfg]]. This includes `try-catch`. + * `try-catch` blocks will be treated specially in the sense that, if a ''finally'' block + * exists, it will not be included in the path from ''start index'' to ''destination index'' + * (however, as ''start index'' marks the beginning of the `try-catch` and ''destination index'' + * everything up to the ''finally block'', ''finally'' statements after the exception handling + * will be included and need to be filtered out later. + * + * @return Returns all found control structures in a flat structure; for the return format, see + * [[CSInfo]]. The elements are returned in a sorted by ascending start index. + */ + protected def findControlStructures(): List[CSInfo] = { + // foundCS stores all found control structures as a triple in the form (start, end, type) + val foundCS = ListBuffer[CSInfo]() + // For a fast loop-up which if statements have already been processed + val processedIfs = mutable.Map[Int, Unit.type]() + val processedSwitches = mutable.Map[Int, Unit.type]() + val startBlock = cfg.startBlock + val stack = mutable.Stack[CFGNode](startBlock) + val seenCFGNodes = mutable.Map[CFGNode, Unit.type]() + seenCFGNodes(startBlock) = Unit + + while (stack.nonEmpty) { + val next = stack.pop() + seenCFGNodes(next) = Unit + + next match { + case bb: BasicBlock ⇒ + for (i ← bb.startPC.to(bb.endPC)) { + cfg.code.instructions(i) match { + case _: If[V] if !processedIfs.contains(i) ⇒ + foundCS.append(processIf(i, processedIfs)) + processedIfs(i) = Unit + case _: Switch[V] if !processedSwitches.contains(i) ⇒ + foundCS.append(processSwitch(i)) + processedSwitches(i) = Unit + case _ ⇒ + } + } + case _ ⇒ + } + + // Add unseen successors + next.successors.filter(!seenCFGNodes.contains(_)).foreach(stack.push) + } + + // Add try-catch information, sort everything in ascending order in terms of the startPC and + // return + foundCS.appendAll(determineTryCatchBounds()) + foundCS.sortBy { case (start, _, _) ⇒ start }.toList + } + + /** + * This function serves as a wrapper function for unified processing of different elements, + * i.e., different types of [[CSInfo]] that are stored in `toTransform`. + * For further information, see [[buildRepetitionPath]], [[buildCondPath]], + * [[buildPathForSwitch]], and [[buildTryCatchPath]]. + */ + protected def buildPathForElement( + toTransform: HierarchicalCSOrder, fill: Boolean + ): (Path, List[(Int, Int)]) = { + val element = toTransform.hierarchy.head._1.get + val start = element._1 + val end = element._2 + if (cfg.code.instructions(start).isInstanceOf[Switch[V]]) { + buildPathForSwitch(start, end, fill) + } else { + element._3 match { + case NestedPathType.Repetition ⇒ + buildRepetitionPath(start, end, fill) + case NestedPathType.CondWithAlternative ⇒ + buildCondPath(start, end, NestedPathType.CondWithAlternative, fill) + case NestedPathType.CondWithoutAlternative ⇒ + buildCondPath(start, end, NestedPathType.CondWithoutAlternative, fill) + case NestedPathType.TryCatchFinally ⇒ + buildTryCatchPath(start, end, fill) + } + } + } + + /** + * This function takes a flat list of control structure information and transforms it into a + * hierarchical order. + * + * @param cs A list of control structure elements that are to be transformed into a hierarchical + * representation. This function assumes, that the control structures are sorted by + * start index in ascending order. + * @return The hierarchical structure. + * + * @note This function assumes that `cs` contains at least one element! + */ + protected def hierarchicallyOrderControlStructures(cs: List[CSInfo]): HierarchicalCSOrder = { + // childrenOf stores seen control structures in the form: parent, children. Note that for + // performance reasons (see foreach loop below), the elements are inserted in reversed order + // in terms of the `cs` order for less loop iterations in the next foreach loop + val childrenOf = mutable.ListBuffer[(CSInfo, ListBuffer[CSInfo])]() + childrenOf.append((cs.head, ListBuffer())) + + // Stores as key a CS and as value the parent element (if an element, e, is not contained in + // parentOf, e does not have a parent + val parentOf = mutable.Map[CSInfo, CSInfo]() + // Find the direct parent of each element (if it exists at all) + cs.tail.foreach { nextCS ⇒ + var nextPossibleParentIndex = 0 + var parent: Option[Int] = None + // Use a while instead of a foreach loop in order to stop when the parent was found + while (parent.isEmpty && nextPossibleParentIndex < childrenOf.length) { + val possibleParent = childrenOf(nextPossibleParentIndex) + // The parent element must fully contain the child + if (nextCS._1 > possibleParent._1._1 && nextCS._1 < possibleParent._1._2) { + parent = Some(nextPossibleParentIndex) + } else { + nextPossibleParentIndex += 1 + } + } + if (parent.isDefined) { + childrenOf(parent.get)._2.append(nextCS) + parentOf(nextCS) = childrenOf(parent.get)._1 + } + childrenOf.prepend((nextCS, ListBuffer())) + } + + // Convert to a map for faster accesses in the following part + val mapChildrenOf = mutable.Map[CSInfo, ListBuffer[CSInfo]]() + childrenOf.foreach { nextCS ⇒ mapChildrenOf(nextCS._1) = nextCS._2 } + + HierarchicalCSOrder(List(( + None, cs.filter(!parentOf.contains(_)).map(buildHierarchy(_, mapChildrenOf)) + ))) + } + /** * Implementations of this function find all paths starting from the sites, given by * `startSites`, within the provided control flow graph, `cfg`. As this is executed within the @@ -207,8 +943,6 @@ trait AbstractPathFinder { * encountered, the finding procedure can be early stopped. Implementations * may or may not use this list (however, they should indicate whether it is * required or not). - * @param cfg The underlying control flow graph which servers as the basis to find the paths. - * * @return Returns all found paths as a [[Path]] object. That means, the return object is a flat * structure, however, captures all hierarchies and (nested) flows. Note that a * [[NestedPathElement]] with only one child can either refer to a loop or an ''if'' @@ -216,6 +950,6 @@ trait AbstractPathFinder { * implementations to attach these information to [[NestedPathElement]]s (so that * procedures using results of this function do not need to re-process). */ - def findPaths(startSites: List[Int], endSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Path + def findPaths(startSites: List[Int], endSite: Int): Path } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index c6a746af00..bd73e8439c 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -1,197 +1,124 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing -import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CFG -import org.opalj.br.cfg.CFGNode -import org.opalj.tac.Goto -import org.opalj.tac.If import org.opalj.tac.Stmt import org.opalj.tac.TACStmts /** * An approach based on an a naive / intuitive traversing of the control flow graph. * + * @param cfg The control flow graph (CFG) on which this instance will operate on. + * * @author Patrick Mell + * + * @note To fill gaps, e.g., from the very first statement of a context, such as a CFG, to the first + * control structure, a consecutive row of path elements are inserted. Arbitrarily inserted + * jumps within the bytecode might lead to a different order than the one computed by this + * class! */ -class DefaultPathFinder extends AbstractPathFinder { +class DefaultPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinder(cfg) { /** - * CSInfo stores information regarding control structures (CS) in the form: Index of the start - * statement of that CS, index of the end statement of that CS and the type. + * This function transforms a hierarchy into a [[Path]]. + * + * @param topElements A list of the elements which are present on the top-most level in the + * hierarchy. + * @param startIndex `startIndex` serves as a way to build a path between the first statement + * (which is not necessarily a control structure) and the very first control + * structure. For example, assume that the first control structure begins at + * statement 5. `startIndex` will then be used to fill the gap `startIndex` + * and 5. + * @param endIndex `endIndex` serves as a way to build a path between the last statement of a + * control structure (which is not necessarily the end of a scope of interest, + * such as a method) and the last statement (e.g., in `cfg`). + * @return Returns the transformed [[Path]]. */ - private type CSInfo = (Int, Int, NestedPathType.Value) - private type CFGType = CFG[Stmt[V], TACStmts[V]] - - private def determineTypeOfIf(cfg: CFGType, stmtIndex: Int): NestedPathType.Value = { - // Is the first condition enough to identify loops? - if (isHeadOfLoop(stmtIndex, cfg.findNaturalLoops(), cfg)) { - NestedPathType.Repetition - } else if (isCondWithoutElse(stmtIndex, cfg)) { - NestedPathType.CondWithoutAlternative - } else { - NestedPathType.CondWithAlternative - } - } - - private def getStartAndEndIndexOfLoop(headIndex: Int, cfg: CFGType): (Int, Int) = { - var startIndex = -1 - var endIndex = -1 - val relevantLoop = cfg.findNaturalLoops().filter(_ ⇒ - isHeadOfLoop(headIndex, cfg.findNaturalLoops(), cfg)) - if (relevantLoop.nonEmpty) { - startIndex = relevantLoop.head.head - endIndex = relevantLoop.head.last - } - (startIndex, endIndex) - } - - private def getStartAndEndIndexOfCondWithAlternative( - branchingSite: Int, cfg: CFGType, processedIfs: mutable.Map[Int, Unit.type] - ): (Int, Int) = { - processedIfs(branchingSite) = Unit - - var endSite = -1 - val stack = mutable.Stack[Int](branchingSite) - while (stack.nonEmpty) { - val popped = stack.pop() - val nextBlock = cfg.bb(popped).successors.map { - case bb: BasicBlock ⇒ bb.startPC - // Handle Catch Nodes? - case _ ⇒ -1 - }.max - var containsIf = false - for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { - processedIfs(i) = Unit - containsIf = true - } + private def hierarchyToPath( + topElements: List[HierarchicalCSOrder], startIndex: Int, endIndex: Int + ): Path = { + val finalPath = ListBuffer[SubPath]() + // For the outer-most call, this is not the start index of the last control structure but of + // the start PC of the first basic block + var indexLastCSEnd = startIndex + + // Recursively transform the hierarchies to paths + topElements.foreach { nextTopEle ⇒ + // Build path up to the next control structure + val nextCSStart = nextTopEle.hierarchy.head._1.get._1 + indexLastCSEnd.until(nextCSStart).foreach { i ⇒ + finalPath.append(FlatPathElement(i)) } - if (containsIf) { - stack.push(nextBlock) + val children = nextTopEle.hierarchy.head._2 + if (children.isEmpty) { + // Recursion anchor: Build path for the correct type + val (subpath, _) = buildPathForElement(nextTopEle, fill = true) + // Control structures consist of only one element (NestedPathElement), thus "head" + // is enough + finalPath.append(subpath.elements.head) } else { - // Find the goto that points after the "else" part (the assumption is that this - // goto is the very last element of the current branch - endSite = cfg.code.instructions(nextBlock - 1).asGoto.targetStmt - 1 - } - } - - (branchingSite, endSite) - } - - private def getStartAndEndIndexOfCondWithoutAlternative( - branchingSite: Int, cfg: CFGType, processedIfs: mutable.Map[Int, Unit.type] - ): (Int, Int) = { - // Find the index of very last element in the if block (here: The goto element; is it always - // present?) - val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt - var endIndex = ifTarget - do { - endIndex -= 1 - } while (cfg.code.instructions(branchingSite).isInstanceOf[Goto]) - - // It is now necessary to collect all ifs that belong to the whole if condition in the - // high-level construct - cfg.bb(ifTarget).predecessors.foreach { - case pred: BasicBlock ⇒ - for (i ← pred.startPC.to(pred.endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { - processedIfs(i) = Unit + val startIndex = nextTopEle.hierarchy.head._1.get._1 + val endIndex = nextTopEle.hierarchy.head._1.get._2 + val childrenPath = hierarchyToPath(children, startIndex, endIndex) + var insertIndex = 0 + val (subpath, startEndPairs) = buildPathForElement(nextTopEle, fill = false) + // npe is the nested path element that was produced above (head is enough as this + // list will always contain only one element, due to fill=false) + val npe = subpath.elements.head.asInstanceOf[NestedPathElement] + val isRepElement = npe.elementType.getOrElse(NestedPathType.TryCatchFinally) == + NestedPathType.Repetition + var lastInsertedIndex = 0 + childrenPath.elements.foreach { nextEle ⇒ + if (isRepElement) { + npe.element.append(nextEle) + } else { + npe.element(insertIndex).asInstanceOf[NestedPathElement].element.append( + nextEle + ) } - } - // How about CatchNodes? - case cn ⇒ println(cn) - } - (branchingSite, endIndex) - } - - private def getTryCatchFinallyInfo(cfg: CFGType): List[CSInfo] = { - // Stores the startPC as key and the index of the end of a catch (or finally if it is - // present); a map is used for faster accesses - val tryInfo = mutable.Map[Int, Int]() - - cfg.catchNodes.foreach { cn ⇒ - if (!tryInfo.contains(cn.startPC)) { - val cnWithSameStartPC = cfg.catchNodes.filter(_.startPC == cn.startPC) - // If there is only one CatchNode for a startPC, i.e., no finally, no other catches, - // the end index can be directly derived from the successors - if (cnWithSameStartPC.tail.isEmpty) { - tryInfo(cn.startPC) = cfg.bb(cn.endPC).successors.map { - case bb: BasicBlock ⇒ bb.startPC - 1 - case _ ⇒ -1 - }.max - } // Otherwise, the largest handlerPC marks the end index - else { - tryInfo(cn.startPC) = cnWithSameStartPC.map(_.handlerPC).max + lastInsertedIndex = nextEle match { + case fpe: FlatPathElement ⇒ fpe.element + case inner: NestedPathElement ⇒ Path.getLastElementInNPE(inner).element + // Compiler wants it but should never be the case! + case _ ⇒ -1 + } + if (lastInsertedIndex >= startEndPairs(insertIndex)._2) { + insertIndex += 1 + } } - } - } - - tryInfo.map { - case (key, value) ⇒ (key, value, NestedPathType.TryCatchFinally) - }.toList - } - - private def processBasicBlock( - cfg: CFGType, stmt: Int, processedIfs: mutable.Map[Int, Unit.type] - ): CSInfo = { - val csType = determineTypeOfIf(cfg, stmt) - val (startIndex, endIndex) = csType match { - case NestedPathType.Repetition ⇒ - processedIfs(stmt) = Unit - getStartAndEndIndexOfLoop(stmt, cfg) - case NestedPathType.CondWithoutAlternative ⇒ - getStartAndEndIndexOfCondWithoutAlternative(stmt, cfg, processedIfs) - // _ covers CondWithAlternative and TryCatchFinally, however, the latter one should - // never be present as the element referring to stmts is / should be an If - case _ ⇒ - getStartAndEndIndexOfCondWithAlternative(stmt, cfg, processedIfs) - } - (startIndex, endIndex, csType) - } - - private def findControlStructures(cfg: CFGType): List[CSInfo] = { - // foundCS stores all found control structures as a triple in the form (start, end, type) - val foundCS = ListBuffer[CSInfo]() - // For a fast loop-up which if statements have already been processed - val processedIfs = mutable.Map[Int, Unit.type]() - val startBlock = cfg.startBlock - val stack = mutable.Stack[CFGNode](startBlock) - val seenCFGNodes = mutable.Map[CFGNode, Unit.type]() - seenCFGNodes(startBlock) = Unit - - while (stack.nonEmpty) { - val next = stack.pop() - seenCFGNodes(next) = Unit - - next match { - case bb: BasicBlock ⇒ - for (i ← bb.startPC.to(bb.endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]] && - !processedIfs.contains(i)) { - foundCS.append(processBasicBlock(cfg, i, processedIfs)) - processedIfs(i) = Unit + // Fill the current NPE if necessary + val currentToInsert = ListBuffer[FlatPathElement]() + if (insertIndex < startEndPairs.length) { + currentToInsert.appendAll((lastInsertedIndex + 1).to( + startEndPairs(insertIndex)._2 + ).map(FlatPathElement)) + if (isRepElement) { + npe.element.appendAll(currentToInsert) + } else { + var insertPos = npe.element(insertIndex).asInstanceOf[NestedPathElement] + insertPos.element.appendAll(currentToInsert) + insertIndex += 1 + // Fill the rest NPEs if necessary + insertIndex.until(startEndPairs.length).foreach { i ⇒ + insertPos = npe.element(i).asInstanceOf[NestedPathElement] + insertPos.element.appendAll( + startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement) + ) } } - case cn: CFGNode ⇒ - println(cn) - case _ ⇒ + } + finalPath.append(subpath.elements.head) } - - // Add unseen successors - next.successors.filter(!seenCFGNodes.contains(_)).foreach(stack.push) + indexLastCSEnd = nextTopEle.hierarchy.head._1.get._2 + 1 } - // Add try-catch information, sort everything in ascending order in terms of the startPC and - // return - foundCS.appendAll(getTryCatchFinallyInfo(cfg)) - foundCS.sortBy { case (start, _, _) ⇒ start }.toList + finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement)) + Path(finalPath.toList) } /** @@ -204,13 +131,18 @@ class DefaultPathFinder extends AbstractPathFinder { * * @see [[AbstractPathFinder.findPaths]] */ - override def findPaths(startSites: List[Int], endSite: Int, cfg: CFGType): Path = { - val startPC = cfg.startBlock.startPC - identity(startPC) - val csInfo = findControlStructures(cfg) - identity(csInfo) - - Path(List(FlatPathElement(0), FlatPathElement(1))) + override def findPaths(startSites: List[Int], endSite: Int): Path = { + val csInfo = findControlStructures() + // In case the are no control structures, return a path from the first to the last element + if (csInfo.isEmpty) { + val indexLastStmt = cfg.code.instructions.length + Path(cfg.startBlock.startPC.until(indexLastStmt).map(FlatPathElement).toList) + } // Otherwise, order the control structures and assign the corresponding path elements + else { + val lastStmtIndex = cfg.code.instructions.length - 1 + val orderedCS = hierarchicallyOrderControlStructures(csInfo) + hierarchyToPath(orderedCS.hierarchy.head._2, cfg.startBlock.startPC, lastStmtIndex) + } } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala index 157cfa214e..f44b1b3cb2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala @@ -1,18 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.fpcf.analyses.string_definition.V import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler +import org.opalj.value.ValueInformation import org.opalj.tac.Assignment import org.opalj.tac.DUVar import org.opalj.tac.ExprStmt import org.opalj.tac.New import org.opalj.tac.Stmt import org.opalj.tac.VirtualFunctionCall -import org.opalj.value.ValueInformation - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer /** * @author Patrick Mell @@ -328,3 +328,23 @@ case class Path(elements: List[SubPath]) { } } + +object Path { + + /** + * Returns the very last [[FlatPathElement]] in this path, respecting any nesting structure. + */ + def getLastElementInNPE(npe: NestedPathElement): FlatPathElement = { + npe.element.last match { + case fpe: FlatPathElement ⇒ fpe + case npe: NestedPathElement ⇒ + npe.element.last match { + case fpe: FlatPathElement ⇒ fpe + case innerNpe: NestedPathElement ⇒ getLastElementInNPE(innerNpe) + case _ ⇒ FlatPathElement(-1) + } + case _ ⇒ FlatPathElement(-1) + } + } + +} From 0c68498223206cc45ca3d1de56c9efc6ecc8aa8b Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 22 Jan 2019 11:26:36 +0100 Subject: [PATCH 123/316] Implemented a path finding procedure which takes into consideration only the relevant part of a method (this is now the default finding procedure for the analysis). --- .../LocalStringDefinitionAnalysis.scala | 22 +-- .../preprocessing/AbstractPathFinder.scala | 154 ++++++++++++++++-- .../preprocessing/DefaultPathFinder.scala | 121 ++------------ .../preprocessing/WindowPathFinder.scala | 75 +++++++++ 4 files changed, 235 insertions(+), 137 deletions(-) create mode 100644 OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala index 6996a61cdc..8d17d08120 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala @@ -1,27 +1,25 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition -import org.opalj.br.analyses.SomeProject -import org.opalj.br.cfg.CFG +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.fpcf.FPCFAnalysis import org.opalj.fpcf.PropertyComputationResult import org.opalj.fpcf.PropertyKind import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.ComputationSpecification +import org.opalj.fpcf.FPCFLazyAnalysisScheduler import org.opalj.fpcf.analyses.string_definition.preprocessing.AbstractPathFinder -import org.opalj.fpcf.analyses.string_definition.preprocessing.DefaultPathFinder import org.opalj.fpcf.analyses.string_definition.preprocessing.PathTransformer import org.opalj.fpcf.Result import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler import org.opalj.fpcf.analyses.string_definition.preprocessing.FlatPathElement import org.opalj.fpcf.analyses.string_definition.preprocessing.NestedPathElement -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.tac.SimpleTACAIKey -import org.opalj.tac.Stmt import org.opalj.fpcf.analyses.string_definition.preprocessing.Path import org.opalj.fpcf.analyses.string_definition.preprocessing.SubPath +import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP @@ -30,12 +28,14 @@ import org.opalj.fpcf.IntermediateResult import org.opalj.fpcf.NoResult import org.opalj.fpcf.Property import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.analyses.string_definition.preprocessing.WindowPathFinder +import org.opalj.br.analyses.SomeProject +import org.opalj.br.cfg.CFG import org.opalj.tac.ExprStmt +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - /** * LocalStringDefinitionAnalysis processes a read operation of a local string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. @@ -84,7 +84,7 @@ class LocalStringDefinitionAnalysis( if (defSites.head < 0) { return Result(data, StringConstancyProperty.lowerBound) } - val pathFinder: AbstractPathFinder = new DefaultPathFinder(cfg) + val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) // If not empty, this very routine can only produce an intermediate result val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 244b050de1..2213295f3f 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -656,15 +656,17 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } /** - * Based on the given `cfg`, this function checks whether a path from node `from` to node `to` - * exists. If so, `true` is returned and `false otherwise`. Optionally, a list of `alreadySeen` - * elements can be passed which influences which paths are to be followed (when assembling a - * path ''p'' and the next node, ''n_p'' in ''p'', is a node that was already seen, the path - * will not be continued in the direction of ''n_p'' (but in other directions that are not in - * `alreadySeen`)). + * Based on the member `cfg` of this instance, this function checks whether a path from node + * `from` to node `to` exists. If so, `true` is returned and `false otherwise`. Optionally, a + * list of `alreadySeen` elements can be passed which influences which paths are to be followed + * (when assembling a path ''p'' and the next node, ''n_p'' in ''p'', is a node that was already + * seen, the path will not be continued in the direction of ''n_p'' (but in other directions + * that are not in `alreadySeen`)). + * + * @note This function assumes that `from` >= 0! */ protected def doesPathExistTo( - from: Int, to: Int, cfg: CFG[Stmt[V], TACStmts[V]], alreadySeen: List[Int] = List() + from: Int, to: Int, alreadySeen: List[Int] = List() ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() @@ -675,7 +677,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val popped = stack.pop() cfg.bb(popped).successors.foreach { nextBlock ⇒ // -1 is okay, as this value will not be processed (due to the flag processBlock) - var startPC, endPC = -1 + var startPC = -1 + var endPC = -1 var processBlock = true nextBlock match { case bb: BasicBlock ⇒ @@ -808,16 +811,19 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * @return Returns all found control structures in a flat structure; for the return format, see * [[CSInfo]]. The elements are returned in a sorted by ascending start index. */ - protected def findControlStructures(): List[CSInfo] = { + protected def findControlStructures(startSites: List[Int], endSite: Int): List[CSInfo] = { // foundCS stores all found control structures as a triple in the form (start, end, type) val foundCS = ListBuffer[CSInfo]() // For a fast loop-up which if statements have already been processed val processedIfs = mutable.Map[Int, Unit.type]() val processedSwitches = mutable.Map[Int, Unit.type]() - val startBlock = cfg.startBlock - val stack = mutable.Stack[CFGNode](startBlock) + val stack = mutable.Stack[CFGNode]() val seenCFGNodes = mutable.Map[CFGNode, Unit.type]() - seenCFGNodes(startBlock) = Unit + + startSites.reverse.foreach { site ⇒ + stack.push(cfg.bb(site)) + seenCFGNodes(cfg.bb(site)) = Unit + } while (stack.nonEmpty) { val next = stack.pop() @@ -839,13 +845,28 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { case _ ⇒ } - // Add unseen successors - next.successors.filter(!seenCFGNodes.contains(_)).foreach(stack.push) + if (next.nodeId == endSite) { + val doesPathExist = stack.filter(_.nodeId >= 0).foldLeft(false) { + (doesExist: Boolean, next: CFGNode) ⇒ + doesExist || doesPathExistTo(next.nodeId, endSite) + } + // In case no more path exists, clear the stack which (=> no more iterations) + if (!doesPathExist) { + stack.clear() + } + } else { + // Add unseen successors + next.successors.filter(!seenCFGNodes.contains(_)).foreach(stack.push) + } } - // Add try-catch information, sort everything in ascending order in terms of the startPC and - // return - foundCS.appendAll(determineTryCatchBounds()) + // Add try-catch (only those that are relevant for the given start and end sites) + // information, sort everything in ascending order in terms of the startPC and return + val relevantTryCatchBlocks = determineTryCatchBounds().filter { + case (tryStart, _, _) ⇒ + startSites.exists(tryStart >= _) && tryStart <= endSite + } + foundCS.appendAll(relevantTryCatchBlocks) foundCS.sortBy { case (start, _, _) ⇒ start }.toList } @@ -928,6 +949,105 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ))) } + /** + * This function transforms a hierarchy into a [[Path]]. + * + * @param topElements A list of the elements which are present on the top-most level in the + * hierarchy. + * @param startIndex `startIndex` serves as a way to build a path between the first statement + * (which is not necessarily a control structure) and the very first control + * structure. For example, assume that the first control structure begins at + * statement 5. `startIndex` will then be used to fill the gap `startIndex` + * and 5. + * @param endIndex `endIndex` serves as a way to build a path between the last statement of a + * control structure (which is not necessarily the end of a scope of interest, + * such as a method) and the last statement (e.g., in `cfg`). + * @return Returns the transformed [[Path]]. + */ + protected def hierarchyToPath( + topElements: List[HierarchicalCSOrder], startIndex: Int, endIndex: Int + ): Path = { + val finalPath = ListBuffer[SubPath]() + // For the outer-most call, this is not the start index of the last control structure but of + // the start PC of the first basic block + var indexLastCSEnd = startIndex + + // Recursively transform the hierarchies to paths + topElements.foreach { nextTopEle ⇒ + // Build path up to the next control structure + val nextCSStart = nextTopEle.hierarchy.head._1.get._1 + indexLastCSEnd.until(nextCSStart).foreach { i ⇒ + finalPath.append(FlatPathElement(i)) + } + + val children = nextTopEle.hierarchy.head._2 + if (children.isEmpty) { + // Recursion anchor: Build path for the correct type + val (subpath, _) = buildPathForElement(nextTopEle, fill = true) + // Control structures consist of only one element (NestedPathElement), thus "head" + // is enough + finalPath.append(subpath.elements.head) + } else { + val startIndex = nextTopEle.hierarchy.head._1.get._1 + val endIndex = nextTopEle.hierarchy.head._1.get._2 + val childrenPath = hierarchyToPath(children, startIndex, endIndex) + var insertIndex = 0 + val (subpath, startEndPairs) = buildPathForElement(nextTopEle, fill = false) + // npe is the nested path element that was produced above (head is enough as this + // list will always contain only one element, due to fill=false) + val npe = subpath.elements.head.asInstanceOf[NestedPathElement] + val isRepElement = npe.elementType.getOrElse(NestedPathType.TryCatchFinally) == + NestedPathType.Repetition + var lastInsertedIndex = 0 + childrenPath.elements.foreach { nextEle ⇒ + if (isRepElement) { + npe.element.append(nextEle) + } else { + npe.element(insertIndex).asInstanceOf[NestedPathElement].element.append( + nextEle + ) + } + + lastInsertedIndex = nextEle match { + case fpe: FlatPathElement ⇒ fpe.element + case inner: NestedPathElement ⇒ Path.getLastElementInNPE(inner).element + // Compiler wants it but should never be the case! + case _ ⇒ -1 + } + if (lastInsertedIndex >= startEndPairs(insertIndex)._2) { + insertIndex += 1 + } + } + // Fill the current NPE if necessary + val currentToInsert = ListBuffer[FlatPathElement]() + if (insertIndex < startEndPairs.length) { + currentToInsert.appendAll((lastInsertedIndex + 1).to( + startEndPairs(insertIndex)._2 + ).map(FlatPathElement)) + if (isRepElement) { + npe.element.appendAll(currentToInsert) + } else { + var insertPos = npe.element(insertIndex).asInstanceOf[NestedPathElement] + insertPos.element.appendAll(currentToInsert) + insertIndex += 1 + // Fill the rest NPEs if necessary + insertIndex.until(startEndPairs.length).foreach { i ⇒ + insertPos = npe.element(i).asInstanceOf[NestedPathElement] + insertPos.element.appendAll( + startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement) + ) + } + } + } + finalPath.append(subpath.elements.head) + } + indexLastCSEnd = nextTopEle.hierarchy.head._1.get._2 + 1 + } + + finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement)) + Path(finalPath.toList) + } + /** * Implementations of this function find all paths starting from the sites, given by * `startSites`, within the provided control flow graph, `cfg`. As this is executed within the diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala index bd73e8439c..91869aabb5 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala @@ -1,17 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing -import scala.collection.mutable.ListBuffer - import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG import org.opalj.tac.Stmt import org.opalj.tac.TACStmts /** - * An approach based on an a naive / intuitive traversing of the control flow graph. + * An approach based on an intuitive traversing of the control flow graph (CFG). This implementation + * will use the CFG to find all paths from the very first statement of the CFG to all end / leaf + * statements in the CFG, ignoring `startSites` and `endSite` passed to + * [[DefaultPathFinder#findPaths]]. * - * @param cfg The control flow graph (CFG) on which this instance will operate on. + * @param cfg The CFG on which this instance will operate on. * * @author Patrick Mell * @@ -22,126 +23,28 @@ import org.opalj.tac.TACStmts */ class DefaultPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinder(cfg) { - /** - * This function transforms a hierarchy into a [[Path]]. - * - * @param topElements A list of the elements which are present on the top-most level in the - * hierarchy. - * @param startIndex `startIndex` serves as a way to build a path between the first statement - * (which is not necessarily a control structure) and the very first control - * structure. For example, assume that the first control structure begins at - * statement 5. `startIndex` will then be used to fill the gap `startIndex` - * and 5. - * @param endIndex `endIndex` serves as a way to build a path between the last statement of a - * control structure (which is not necessarily the end of a scope of interest, - * such as a method) and the last statement (e.g., in `cfg`). - * @return Returns the transformed [[Path]]. - */ - private def hierarchyToPath( - topElements: List[HierarchicalCSOrder], startIndex: Int, endIndex: Int - ): Path = { - val finalPath = ListBuffer[SubPath]() - // For the outer-most call, this is not the start index of the last control structure but of - // the start PC of the first basic block - var indexLastCSEnd = startIndex - - // Recursively transform the hierarchies to paths - topElements.foreach { nextTopEle ⇒ - // Build path up to the next control structure - val nextCSStart = nextTopEle.hierarchy.head._1.get._1 - indexLastCSEnd.until(nextCSStart).foreach { i ⇒ - finalPath.append(FlatPathElement(i)) - } - - val children = nextTopEle.hierarchy.head._2 - if (children.isEmpty) { - // Recursion anchor: Build path for the correct type - val (subpath, _) = buildPathForElement(nextTopEle, fill = true) - // Control structures consist of only one element (NestedPathElement), thus "head" - // is enough - finalPath.append(subpath.elements.head) - } else { - val startIndex = nextTopEle.hierarchy.head._1.get._1 - val endIndex = nextTopEle.hierarchy.head._1.get._2 - val childrenPath = hierarchyToPath(children, startIndex, endIndex) - var insertIndex = 0 - val (subpath, startEndPairs) = buildPathForElement(nextTopEle, fill = false) - // npe is the nested path element that was produced above (head is enough as this - // list will always contain only one element, due to fill=false) - val npe = subpath.elements.head.asInstanceOf[NestedPathElement] - val isRepElement = npe.elementType.getOrElse(NestedPathType.TryCatchFinally) == - NestedPathType.Repetition - var lastInsertedIndex = 0 - childrenPath.elements.foreach { nextEle ⇒ - if (isRepElement) { - npe.element.append(nextEle) - } else { - npe.element(insertIndex).asInstanceOf[NestedPathElement].element.append( - nextEle - ) - } - - lastInsertedIndex = nextEle match { - case fpe: FlatPathElement ⇒ fpe.element - case inner: NestedPathElement ⇒ Path.getLastElementInNPE(inner).element - // Compiler wants it but should never be the case! - case _ ⇒ -1 - } - if (lastInsertedIndex >= startEndPairs(insertIndex)._2) { - insertIndex += 1 - } - } - // Fill the current NPE if necessary - val currentToInsert = ListBuffer[FlatPathElement]() - if (insertIndex < startEndPairs.length) { - currentToInsert.appendAll((lastInsertedIndex + 1).to( - startEndPairs(insertIndex)._2 - ).map(FlatPathElement)) - if (isRepElement) { - npe.element.appendAll(currentToInsert) - } else { - var insertPos = npe.element(insertIndex).asInstanceOf[NestedPathElement] - insertPos.element.appendAll(currentToInsert) - insertIndex += 1 - // Fill the rest NPEs if necessary - insertIndex.until(startEndPairs.length).foreach { i ⇒ - insertPos = npe.element(i).asInstanceOf[NestedPathElement] - insertPos.element.appendAll( - startEndPairs(i)._1.to(startEndPairs(i)._2).map(FlatPathElement) - ) - } - } - } - finalPath.append(subpath.elements.head) - } - indexLastCSEnd = nextTopEle.hierarchy.head._1.get._2 + 1 - } - - finalPath.appendAll(indexLastCSEnd.to(endIndex).map(FlatPathElement)) - Path(finalPath.toList) - } - /** * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` * and, based on that, determines in what relation a statement / instruction is with its * predecessors / successors. * The paths contain all instructions, not only those that modify a [[StringBuilder]] / * [[StringBuffer]] object. - * For this implementation, `startSites` as well as `endSite` are required! + * In this implementation, `startSites` as well as `endSite` are ignored, i.e., it is fine to + * pass any values for these two. * * @see [[AbstractPathFinder.findPaths]] */ override def findPaths(startSites: List[Int], endSite: Int): Path = { - val csInfo = findControlStructures() + val startSite = cfg.startBlock.startPC + val endSite = cfg.code.instructions.length - 1 + val csInfo = findControlStructures(List(startSite), endSite) // In case the are no control structures, return a path from the first to the last element if (csInfo.isEmpty) { - val indexLastStmt = cfg.code.instructions.length - Path(cfg.startBlock.startPC.until(indexLastStmt).map(FlatPathElement).toList) + Path(cfg.startBlock.startPC.until(endSite).map(FlatPathElement).toList) } // Otherwise, order the control structures and assign the corresponding path elements else { - val lastStmtIndex = cfg.code.instructions.length - 1 val orderedCS = hierarchicallyOrderControlStructures(csInfo) - hierarchyToPath(orderedCS.hierarchy.head._2, cfg.startBlock.startPC, lastStmtIndex) + hierarchyToPath(orderedCS.hierarchy.head._2, startSite, endSite) } } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala new file mode 100644 index 0000000000..3cffd70ade --- /dev/null +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala @@ -0,0 +1,75 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses.string_definition.preprocessing + +import org.opalj.fpcf.analyses.string_definition.V +import org.opalj.br.cfg.CFG +import org.opalj.tac.If +import org.opalj.tac.Stmt +import org.opalj.tac.Switch +import org.opalj.tac.TACStmts + +/** + * An approach based on an intuitive traversing of the control flow graph (CFG). This implementation + * will use the CFG to find all paths from the given `startSites` to the `endSite`. ("Window" as + * only part of the whole CFG is considered.) + * + * @param cfg The CFG on which this instance will operate on. + * + * @author Patrick Mell + * + * @note To fill gaps, e.g., from the very first statement of a context, such as a CFG, to the first + * control structure, a consecutive row of path elements are inserted. Arbitrarily inserted + * jumps within the bytecode might lead to a different order than the one computed by this + * class! + */ +class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinder(cfg) { + + /** + * This implementation finds all paths based on an a naive / intuitive traversing of the `cfg` + * and, based on that, determines in what relation a statement / instruction is with its + * predecessors / successors. + * The paths contain all instructions, not only those that modify a [[StringBuilder]] / + * [[StringBuffer]] object. + * For this implementation, `startSites` as well as `endSite` are required! + * + * @see [[AbstractPathFinder.findPaths]] + */ + override def findPaths(startSites: List[Int], endSite: Int): Path = { + // If there are multiple start sites, find the parent "if" or "switch" and use that as a + // start site + var startSite: Option[Int] = None + if (startSites.tail.nonEmpty) { + var nextStmt = startSites.min + while (nextStmt >= 0 && startSite.isEmpty) { + cfg.code.instructions(nextStmt) match { + case iff: If[V] if startSites.contains(iff.targetStmt) ⇒ + startSite = Some(nextStmt) + case _: Switch[V] ⇒ + val (startSwitch, endSwitch, _) = processSwitch(nextStmt) + val isParentSwitch = startSites.forall { + nextStartSite ⇒ nextStartSite >= startSwitch && nextStartSite <= endSwitch + } + if (isParentSwitch) { + startSite = Some(nextStmt) + } + case _ ⇒ + } + nextStmt -= 1 + } + } else { + startSite = Some(startSites.head) + } + + val csInfo = findControlStructures(List(startSite.get), endSite) + // In case the are no control structures, return a path from the first to the last element + if (csInfo.isEmpty) { + val indexLastStmt = cfg.code.instructions.length + Path(cfg.startBlock.startPC.until(indexLastStmt).map(FlatPathElement).toList) + } // Otherwise, order the control structures and assign the corresponding path elements + else { + val orderedCS = hierarchicallyOrderControlStructures(csInfo) + hierarchyToPath(orderedCS.hierarchy.head._2, startSite.get, endSite) + } + } + +} From b5dbbf251fe5f016a6c9960f1853ecbdcee08bf0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 09:33:08 +0100 Subject: [PATCH 124/316] Had to slightly refine the interpretAppendCall method (for the reason see the comment). --- .../interpretation/VirtualFunctionCallInterpreter.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala index 06b3e82c0f..2835524a51 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala @@ -88,6 +88,10 @@ class VirtualFunctionCallInterpreter( // produce a result (= empty list), the if part else if (receiverValues.isEmpty) { Some(List(appendValue.get)) + } // The append value might be empty, if the site has already been processed (then this + // information will come from another StringConstancyInformation object + else if (appendValue.isEmpty) { + Some(receiverValues) } // Receiver and parameter information are available => Combine them else { Some(receiverValues.map { nextSci ⇒ From e87c14fd2536282216fee03c33076846c4aa081d Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 09:33:47 +0100 Subject: [PATCH 125/316] Various minor fixes on the path finding procedures (now the whole JDK can be analyzed with the re-implementation). --- .../preprocessing/AbstractPathFinder.scala | 146 +++++++++++++++--- .../preprocessing/WindowPathFinder.scala | 3 + 2 files changed, 125 insertions(+), 24 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 2213295f3f..0b6b00a46a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -12,6 +12,8 @@ import org.opalj.tac.TACStmts import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.tac.Goto +import org.opalj.tac.ReturnValue import org.opalj.tac.Switch /** @@ -43,7 +45,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''e1''. */ protected case class HierarchicalCSOrder( - hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] ) /** @@ -87,9 +89,26 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (containsIf) { stack.push(nextBlock) } else { - // Find the goto that points after the "else" part (the assumption is that this - // goto is the very last element of the current branch - endSite = cfg.code.instructions(nextBlock - 1).asGoto.targetStmt - 1 + cfg.code.instructions(nextBlock - 1) match { + case goto: Goto ⇒ + // Find the goto that points after the "else" part (the assumption is that + // this goto is the very last element of the current branch + endSite = goto.targetStmt - 1 + case _ ⇒ + // No goto available => Jump after next block + var nextIf: Option[If[V]] = None + var i = nextBlock + while (nextIf.isEmpty) { + cfg.code.instructions(i) match { + case iff: If[V] ⇒ + nextIf = Some(iff) + processedIfs(i) = Unit + case _ ⇒ + } + i += 1 + } + endSite = nextIf.get.targetStmt + } } } @@ -124,8 +143,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { }.max var nextIfIndex = -1 + val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt for (i ← cfg.bb(nextPossibleIfBlock).startPC.to(cfg.bb(nextPossibleIfBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + // The second condition is necessary to detect two consecutive "if"s (not in an else-if + // relation) + if (cfg.code.instructions(i).isInstanceOf[If[V]] && ifTarget != i) { processedIfs(i) = Unit nextIfIndex = i } @@ -139,7 +161,6 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { endIndex = newEndIndex } - val ifTarget = cfg.code.instructions(branchingSite).asInstanceOf[If[V]].targetStmt // It might be that the "i"f is the very last element in a loop; in this case, it is a // little bit more complicated to find the end of the "if": Go up to the element that points // to the if target element @@ -147,15 +168,16 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val toVisit = mutable.Stack[Int](branchingSite) while (toVisit.nonEmpty) { val popped = toVisit.pop() - val successors = cfg.bb(popped).successors - if (successors.size == 1 && successors.head.asBasicBlock.startPC == ifTarget) { + val relevantSuccessors = cfg.bb(popped).successors.filter { + _.isInstanceOf[BasicBlock] + }.map(_.asBasicBlock) + if (relevantSuccessors.size == 1 && relevantSuccessors.head.startPC == ifTarget) { endIndex = cfg.bb(popped).endPC toVisit.clear() } else { - toVisit.pushAll(successors.filter { - case bb: BasicBlock ⇒ bb.nodeId != ifTarget - case _ ⇒ false - }.map(_.asBasicBlock.startPC)) + toVisit.pushAll(relevantSuccessors.filter { + _.nodeId != ifTarget + }.map(_.startPC)) } } } @@ -185,6 +207,26 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { (branchingSite, endIndex) } + /** + * This method finds the very first return value after (including) the given start position. + * + * @param startPos The index of the position to start with. + * @return Returns either the index of the very first found [[ReturnValue]] or the index of the + * very last statement within the instructions if no [[ReturnValue]] could be found. + */ + private def findNextReturn(startPos: Int): Int = { + var returnPos = startPos + var foundReturn = false + while (!foundReturn && returnPos < cfg.code.instructions.length) { + if (cfg.code.instructions(returnPos).isInstanceOf[ReturnValue[V]]) { + foundReturn = true + } else { + returnPos += 1 + } + } + returnPos + } + /** * This function detects all `try-catch` blocks in the given CFG, extracts the indices of the * first statement for each `try` as the as well as the indices of the last statements of the @@ -223,10 +265,20 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } // If there is only one CatchNode for a startPC, i.e., no finally, no other // catches, the end index can be directly derived from the successors else if (cnSameStartPC.tail.isEmpty && !isThrowable) { - tryInfo(cn.startPC) = cfg.bb(cn.endPC).successors.map { - case bb: BasicBlock ⇒ bb.startPC - 1 - case _ ⇒ -1 - }.max + if (cn.endPC > -1) { + var end = cfg.bb(cn.endPC).successors.map { + case bb: BasicBlock ⇒ bb.startPC - 1 + case _ ⇒ -1 + }.max + if (end == -1) { + end = findNextReturn(cn.handlerPC) + } + tryInfo(cn.startPC) = end + } // -1 might be the case if the catch returns => Find that return and use + // it as the end of the range + else { + findNextReturn(cn.handlerPC) + } } // Otherwise, the index after the try and all catches marks the end index (-1 // to not already get the start index of the successor) else { @@ -439,7 +491,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { throwableElement = Some(cn) } else { catchBlockStartPCs.append(cn.handlerPC) - if (cn.catchType.isEmpty) { + if (cn.startPC == start && cn.catchType.isEmpty) { hasFinallyBlock = true } } @@ -452,7 +504,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // This is for the catch block startEndPairs.append((throwCatch.get.startPC, throwCatch.get.endPC - 1)) } - } else { + } else if (startEndPairs.nonEmpty) { var numElementsFinally = 0 if (hasFinallyBlock) { // Find out, how many elements the finally block has @@ -478,6 +530,32 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ) } } + } // In some cases (sometimes when a throwable is involved) the successors are no catch + // nodes => Find the bounds now + else { + val cn = cfg.catchNodes.filter(_.startPC == start).head + startEndPairs.append((cn.startPC, cn.endPC - 1)) + val endOfCatch = cfg.code.instructions(cn.handlerPC - 1) match { + case goto: Goto ⇒ + // The first statement after the catches; it might be less than cn.startPC in + // case it refers to a loop. If so, use the "if" to find the end + var indexFirstAfterCatch = goto.targetStmt + if (indexFirstAfterCatch < cn.startPC) { + var iff: Option[If[V]] = None + var i = indexFirstAfterCatch + while (iff.isEmpty) { + cfg.code.instructions(i) match { + case foundIf: If[V] ⇒ iff = Some(foundIf) + case _ ⇒ + } + i += 1 + } + indexFirstAfterCatch = iff.get.targetStmt + } + indexFirstAfterCatch + case _ ⇒ findNextReturn(cn.handlerPC) + } + startEndPairs.append((cn.endPC, endOfCatch)) } val subPaths = ListBuffer[SubPath]() @@ -584,8 +662,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -670,7 +748,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_)= Unit) + alreadySeen.foreach(seenNodes(_) = Unit) seenNodes(from) = Unit while (stack.nonEmpty) { @@ -813,7 +891,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { */ protected def findControlStructures(startSites: List[Int], endSite: Int): List[CSInfo] = { // foundCS stores all found control structures as a triple in the form (start, end, type) - val foundCS = ListBuffer[CSInfo]() + var foundCS = ListBuffer[CSInfo]() // For a fast loop-up which if statements have already been processed val processedIfs = mutable.Map[Int, Unit.type]() val processedSwitches = mutable.Map[Int, Unit.type]() @@ -860,12 +938,32 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } } + // It might be that some control structures can be removed as they are not in the relevant + // range + foundCS = foundCS.filterNot { + case (start, end, _) ⇒ + (startSites.forall(start > _) && endSite < start) || + (startSites.forall(_ < start) && startSites.forall(_ > end)) + } + // Add try-catch (only those that are relevant for the given start and end sites) - // information, sort everything in ascending order in terms of the startPC and return - val relevantTryCatchBlocks = determineTryCatchBounds().filter { + // information + var relevantTryCatchBlocks = determineTryCatchBounds() + // Filter out all blocks that completely surround the given start and end sites + relevantTryCatchBlocks = relevantTryCatchBlocks.filter { + case (tryStart, tryEnd, _) ⇒ + val tryCatchParts = buildTryCatchPath(tryStart, tryEnd, fill = false) + !tryCatchParts._2.exists { + case (nextInnerStart, nextInnerEnd) ⇒ + startSites.forall(_ >= nextInnerStart) && endSite <= nextInnerEnd + } + } + // Keep the try-catch blocks that are (partially) within the start and end sites + relevantTryCatchBlocks = relevantTryCatchBlocks.filter { case (tryStart, _, _) ⇒ startSites.exists(tryStart >= _) && tryStart <= endSite } + foundCS.appendAll(relevantTryCatchBlocks) foundCS.sortBy { case (start, _, _) ⇒ start }.toList } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala index 3cffd70ade..ef2daecba4 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala @@ -56,6 +56,9 @@ class WindowPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) extends AbstractPathFinde } nextStmt -= 1 } + if (startSite.isEmpty) { + startSite = Some(0) + } } else { startSite = Some(startSites.head) } From 66ca5eed741e7a5b59d1d5779a2082478edb33c9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 17:23:58 +0100 Subject: [PATCH 126/316] Slightly updated the runner (no new or modifier logic, however). --- .../info/StringAnalysisReflectiveCalls.scala | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index 4cc330a597..95573ab521 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -68,17 +68,17 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { */ private val relevantMethodNames = List( // The following is for the Java Reflection API - // "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", - // "java.lang.Class#getField", "java.lang.Class#getDeclaredField", - // "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" + "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", + "java.lang.Class#getField", "java.lang.Class#getDeclaredField", + "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" // The following is for the javax.crypto API - "javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", - "javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", - "javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", - "javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", - "javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", - "javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", - "javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" + //"javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", + //"javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", + //"javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", + //"javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", + //"javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", + //"javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", + //"javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" ) /** @@ -86,14 +86,10 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { * analysis crash. */ private val ignoreMethods = List( - // Check the found paths on this one - // "com/sun/corba/se/impl/orb/ORBImpl#setDebugFlags", - "com/oracle/webservices/internal/api/message/BasePropertySet$1#run", - "java/net/URL#getURLStreamHandler", - "java/net/URLConnection#lookupContentHandlerClassFor", - // Non rt.jar - "com/sun/javafx/property/PropertyReference#reflect", - "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform" + // For the next one, there should be a \w inside the second string + // "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform", + // Check this result: + //"com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize" ) override def title: String = "String Analysis for Reflective Calls" @@ -150,9 +146,9 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = s"${method.classFile.thisType.fqn}#${method.name}" if (!ignoreMethods.contains(fqnMethodName)) { - println( - s"Processing ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" - ) + //println( + // s"Processing ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" + //) // Loop through all parameters and start the analysis for those that take a string call.descriptor.parameterTypes.zipWithIndex.foreach { case (ft, index) ⇒ From 52819687cdc9e9da2a59ce06e28abc667d0b0b83 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 23 Jan 2019 17:25:08 +0100 Subject: [PATCH 127/316] Optimized the imports. --- .../preprocessing/AbstractPathFinder.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala index 0b6b00a46a..6d90582558 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala @@ -1,20 +1,20 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.analyses.string_definition.preprocessing +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG import org.opalj.br.cfg.CFGNode -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.tac.If -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - import org.opalj.tac.Goto +import org.opalj.tac.If import org.opalj.tac.ReturnValue +import org.opalj.tac.Stmt import org.opalj.tac.Switch +import org.opalj.tac.TACStmts /** * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the From d9291f54d518ee3e41f226377d84b083352bb1fe Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 17:09:45 +0100 Subject: [PATCH 128/316] Changes required by the latest 'develop' branch. --- .../support/info/ClassUsageAnalysis.scala | 15 ++- .../info/StringAnalysisReflectiveCalls.scala | 51 ++++---- .../StringConstancyLevel.java | 2 +- .../string_definition/StringDefinitions.java | 2 +- .../fpcf/LocalStringDefinitionTest.scala | 42 ++++-- .../LocalStringDefinitionMatcher.scala | 2 +- .../properties/StringConstancyProperty.scala | 15 +-- .../StringConstancyInformation.scala | 11 +- .../StringConstancyLevel.scala | 2 +- .../StringConstancyType.scala | 2 +- .../string_definition}/StringTree.scala | 11 +- .../string_definition/properties.scala} | 4 +- .../StringConstancyLevelTests.scala | 9 +- .../LocalStringDefinitionAnalysis.scala | 121 ++++++++++-------- .../AbstractStringInterpreter.scala | 10 +- .../interpretation/ArrayInterpreter.scala | 15 ++- .../BinaryExprInterpreter.scala | 10 +- .../interpretation/FieldInterpreter.scala | 12 +- .../interpretation/GetStaticInterpreter.scala | 10 +- .../IntegerValueInterpreter.scala | 10 +- .../InterpretationHandler.scala | 12 +- .../interpretation/NewInterpreter.scala | 10 +- .../NonVirtualFunctionCallInterpreter.scala | 10 +- .../NonVirtualMethodCallInterpreter.scala | 10 +- .../StaticFunctionCallInterpreter.scala | 14 +- .../StringConstInterpreter.scala | 12 +- .../VirtualFunctionCallInterpreter.scala | 16 +-- .../VirtualMethodCallInterpreter.scala | 10 +- .../preprocessing/AbstractPathFinder.scala | 12 +- .../preprocessing/DefaultPathFinder.scala | 4 +- .../string_analysis}/preprocessing/Path.scala | 6 +- .../preprocessing/PathTransformer.scala | 32 ++--- .../preprocessing/WindowPathFinder.scala | 4 +- .../string_analysis/string_analysis.scala} | 6 +- 34 files changed, 274 insertions(+), 240 deletions(-) rename OPAL/br/src/main/scala/org/opalj/{ => br}/fpcf/properties/StringConstancyProperty.scala (81%) rename OPAL/br/src/main/scala/org/opalj/{fpcf/string_definition/properties => br/fpcf/properties/string_definition}/StringConstancyInformation.scala (88%) rename OPAL/br/src/main/scala/org/opalj/{fpcf/string_definition/properties => br/fpcf/properties/string_definition}/StringConstancyLevel.scala (97%) rename OPAL/br/src/main/scala/org/opalj/{fpcf/string_definition/properties => br/fpcf/properties/string_definition}/StringConstancyType.scala (95%) rename OPAL/br/src/main/scala/org/opalj/{fpcf/string_definition/properties => br/fpcf/properties/string_definition}/StringTree.scala (98%) rename OPAL/br/src/main/scala/org/opalj/{fpcf/string_definition/properties/package.scala => br/fpcf/properties/string_definition/properties.scala} (74%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/LocalStringDefinitionAnalysis.scala (77%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/AbstractStringInterpreter.scala (79%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/ArrayInterpreter.scala (90%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/BinaryExprInterpreter.scala (88%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/FieldInterpreter.scala (78%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/GetStaticInterpreter.scala (78%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/IntegerValueInterpreter.scala (70%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/InterpretationHandler.scala (96%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/NewInterpreter.scala (82%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/NonVirtualFunctionCallInterpreter.scala (77%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/NonVirtualMethodCallInterpreter.scala (92%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/StaticFunctionCallInterpreter.scala (78%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/StringConstInterpreter.scala (75%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/VirtualFunctionCallInterpreter.scala (95%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/interpretation/VirtualMethodCallInterpreter.scala (81%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/preprocessing/AbstractPathFinder.scala (99%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/preprocessing/DefaultPathFinder.scala (94%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/preprocessing/Path.scala (98%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/preprocessing/PathTransformer.scala (86%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis}/preprocessing/WindowPathFinder.scala (96%) rename OPAL/{ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala => tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala} (89%) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala index c14dc764cf..ca15ff7614 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala @@ -1,24 +1,25 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.support.info +import scala.annotation.switch + import java.net.URL +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.log.GlobalLogContext +import org.opalj.log.OPALLogger import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project import org.opalj.br.analyses.ReportableAnalysisResult -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.log.GlobalLogContext -import org.opalj.log.OPALLogger import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.VirtualFunctionCall - -import scala.annotation.switch -import scala.collection.mutable -import scala.collection.mutable.ListBuffer +import org.opalj.tac.fpcf.analyses.cg.V /** * Analyzes a project for how a particular class is used within that project. This means that this diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index 95573ab521..65ba2db52d 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -8,16 +8,9 @@ import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.FPCFAnalysesManagerKey -import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.fpcf.FinalE +import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.PropertyStoreKey -import org.opalj.fpcf.analyses.string_definition.P -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.properties.StringConstancyProperty import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project @@ -27,12 +20,20 @@ import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.ReferenceType import org.opalj.br.instructions.INVOKEVIRTUAL import org.opalj.br.Method +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.fpcf.analyses.string_analysis.LazyStringDefinitionAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.P +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * Analyzes a project for calls provided by the Java Reflection API and tries to determine which @@ -71,25 +72,24 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", "java.lang.Class#getField", "java.lang.Class#getDeclaredField", "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" - // The following is for the javax.crypto API - //"javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", - //"javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", - //"javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", - //"javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", - //"javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", - //"javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", - //"javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" + // The following is for the javax.crypto API + //"javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", + //"javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", + //"javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", + //"javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", + //"javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", + //"javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", + //"javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" ) /** * A list of fully-qualified method names that are to be skipped, e.g., because they make the * analysis crash. */ - private val ignoreMethods = List( - // For the next one, there should be a \w inside the second string - // "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform", - // Check this result: - //"com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize" + private val ignoreMethods = List( // For the next one, there should be a \w inside the second string + // "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform", + // Check this result: + //"com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize" ) override def title: String = "String Analysis for Reflective Calls" @@ -157,9 +157,8 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { val e = (duvar, method) ps(e, StringConstancyProperty.key) match { - case FinalEP(_, prop) ⇒ resultMap(call.name).append( - prop.stringConstancyInformation - ) + case FinalE(prop: StringConstancyProperty) ⇒ + resultMap(call.name).append(prop.stringConstancyInformation) case _ ⇒ entities.append( (e, buildFQMethodName(call.declaringClass, call.name)) ) @@ -169,7 +168,7 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { while (entities.nonEmpty) { val nextEntity = entities.head ps.properties(nextEntity._1).toIndexedSeq.foreach { - case FinalEP(_, prop: StringConstancyProperty) ⇒ + case FinalP(prop: StringConstancyProperty) ⇒ resultMap(nextEntity._2).append( prop.stringConstancyInformation ) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java index c8831f47ff..7cfa2716c6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java @@ -3,7 +3,7 @@ /** * Java annotations do not work with Scala enums, such as - * {@link org.opalj.fpcf.string_definition.properties.StringConstancyLevel}. Thus, this enum. + * {@link org.opalj.br.fpcf.properties.string_definition.properties.StringConstancyLevel}. Thus, this enum. * * @author Patrick Mell */ diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java index 6047ce06b3..035bdf9757 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java @@ -31,7 +31,7 @@ /** * A regexp like string that describes the element(s) that are expected. For the rules, refer to - * {@link org.opalj.fpcf.string_definition.properties.StringTreeElement}. + * {@link org.opalj.br.fpcf.properties.string_definition.properties.StringTreeElement}. * For example, "(* | (hello | world)^5)" describes a string which can 1) either be any string * or 2) a five time concatenation of "hello" and/or "world". */ diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala index 39fdeba571..2eb147a9b1 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -4,22 +4,23 @@ package fpcf import java.io.File +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method import org.opalj.br.cfg.CFG import org.opalj.br.Annotations -import org.opalj.fpcf.analyses.cg.V -import org.opalj.fpcf.analyses.string_definition.LazyStringDefinitionAnalysis -import org.opalj.fpcf.analyses.string_definition.LocalStringDefinitionAnalysis -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.FPCFAnalysesManagerKey import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer +import org.opalj.tac.fpcf.analyses.string_analysis.LazyStringDefinitionAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringDefinitionAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * Tests whether the StringTrackingAnalysis works correctly. @@ -28,6 +29,16 @@ import scala.collection.mutable.ListBuffer */ class LocalStringDefinitionTest extends PropertiesTest { + // val analyses: List[FPCFAnalysisScheduler] = List( + // RTACallGraphAnalysisScheduler, + // TriggeredStaticInitializerAnalysis, + // TriggeredInstantiatedTypesAnalysis, + // TriggeredLoadedClassesAnalysis, + // TACAITransformer, + // LazyCalleesAnalysis(Set(StandardInvokeCallees)), + // LazyStringDefinitionAnalysis + // ) + /** * @return Returns all relevant project files (NOT including library files) to run the tests. */ @@ -85,15 +96,20 @@ class LocalStringDefinitionTest extends PropertiesTest { describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { val p = Project(getRelevantProjectFiles, Array[File]()) - val ps = p.get(org.opalj.fpcf.PropertyStoreKey) - ps.setupPhase(Set(StringConstancyProperty)) + + val manager = p.get(FPCFAnalysesManagerKey) + val (ps, _) = manager.runAll(LazyStringDefinitionAnalysis) + // val testContext = executeAnalyses(analyses) + val testContext = TestContext(p, ps, List(new LocalStringDefinitionAnalysis(p))) + + // val as = TestContext(p, ps, a :: testContext.analyses) LazyStringDefinitionAnalysis.init(p, ps) LazyStringDefinitionAnalysis.schedule(ps, null) + val tacProvider = p.get(DefaultTACAIKey) // We need a "method to entity" matching for the evaluation (see further below) val m2e = mutable.HashMap[Method, Entity]() - val tacProvider = p.get(DefaultTACAIKey) p.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( @@ -121,10 +137,8 @@ class LocalStringDefinitionTest extends PropertiesTest { ) } } - validateProperties( - TestContext(p, ps, Set(new LocalStringDefinitionAnalysis(p))), - eas, Set("StringConstancy") - ) + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set("StringConstancy")) ps.waitOnPhaseCompletion() } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala index e100f2b85a..fd22239447 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala @@ -6,7 +6,7 @@ import org.opalj.br.AnnotationLike import org.opalj.br.ObjectType import org.opalj.fpcf.properties.AbstractPropertyMatcher import org.opalj.fpcf.Property -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.StringConstancyProperty /** * Matches local variable's `StringConstancy` property. The match is successful if the diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala similarity index 81% rename from OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index 2ad2f6351a..b73fc3f3ae 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -1,19 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties -import org.opalj.br.Field +package org.opalj.br.fpcf.properties + import org.opalj.fpcf.Entity -import org.opalj.fpcf.EPS import org.opalj.fpcf.FallbackReason import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType sealed trait StringConstancyPropertyMetaInformation extends PropertyMetaInformation { - type Self = StringConstancyProperty + final type Self = StringConstancyProperty } class StringConstancyProperty( @@ -40,8 +39,6 @@ object StringConstancyProperty extends StringConstancyPropertyMetaInformation { // TODO: Using simple heuristics, return a better value for some easy cases lowerBound }, - (_, eps: EPS[Field, StringConstancyProperty]) ⇒ eps.ub, - (_: PropertyStore, _: Entity) ⇒ None ) } diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala similarity index 88% rename from OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 88ae7626fe..5139e69bce 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -1,17 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.string_definition.properties +package org.opalj.br.fpcf.properties.string_definition -import org.opalj.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.StringConstancyProperty /** * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this * parameter can be omitted. + * * @author Patrick Mell */ case class StringConstancyInformation( - constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, - constancyType: StringConstancyType.Value = StringConstancyType.APPEND, - possibleStrings: String = "" + constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, + constancyType: StringConstancyType.Value = StringConstancyType.APPEND, + possibleStrings: String = "" ) /** diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala similarity index 97% rename from OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala index e82feac3d5..289b52c23c 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyLevel.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyLevel.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.string_definition.properties +package org.opalj.br.fpcf.properties.string_definition /** * Values in this enumeration represent the granularity of used strings. diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala similarity index 95% rename from OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala index 87841c687a..3227d75e4c 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringConstancyType.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyType.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.string_definition.properties +package org.opalj.br.fpcf.properties.string_definition /** * Values in this enumeration represent how a string / string container, such as [[StringBuilder]], diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala similarity index 98% rename from OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala index 8d9b854b26..b8e7bd422e 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala @@ -1,13 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.string_definition.properties - -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation.InfiniteRepetitionSymbol +package org.opalj.br.fpcf.properties.string_definition import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.br.fpcf.properties.properties.StringTree +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.InfiniteRepetitionSymbol + /** - * Super type for modeling nodes and leafs of [[StringTree]]s. + * Super type for modeling nodes and leafs of [[org.opalj.br.fpcf.properties.properties.StringTree]]s. * * @author Patrick Mell */ @@ -270,7 +271,7 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme * the information stored in this tree. * * @param preprocess If set to true, `this` tree will be preprocess, i.e., it will be - * simplified and repetition elements be grouped. Note that preprocessing + * simplified and repetition elements be grouped. Note that pre-processing * changes `this` instance! * @return A [[StringConstancyInformation]] instance that flatly describes this tree. */ diff --git a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala similarity index 74% rename from OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala rename to OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala index a3053c27bf..df9a18d40a 100644 --- a/OPAL/br/src/main/scala/org/opalj/fpcf/string_definition/properties/package.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/properties.scala @@ -1,5 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.string_definition +package org.opalj.br.fpcf.properties + +import org.opalj.br.fpcf.properties.string_definition.StringTreeElement package object properties { diff --git a/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala b/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala index b9268749a5..ac4f3d0984 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/string_definition/StringConstancyLevelTests.scala @@ -1,12 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.br.string_definition -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.CONSTANT -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.DYNAMIC -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel.PARTIALLY_CONSTANT import org.scalatest.FunSuite +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.CONSTANT +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.PARTIALLY_CONSTANT + /** * Tests for [[StringConstancyLevel]] methods. * diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringDefinitionAnalysis.scala similarity index 77% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringDefinitionAnalysis.scala index 8d17d08120..ff6980dfea 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/LocalStringDefinitionAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringDefinitionAnalysis.scala @@ -1,40 +1,42 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition +package org.opalj.tac.fpcf.analyses.string_analysis import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.FPCFAnalysis -import org.opalj.fpcf.PropertyComputationResult -import org.opalj.fpcf.PropertyKind -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.ComputationSpecification -import org.opalj.fpcf.FPCFLazyAnalysisScheduler -import org.opalj.fpcf.analyses.string_definition.preprocessing.AbstractPathFinder -import org.opalj.fpcf.analyses.string_definition.preprocessing.PathTransformer -import org.opalj.fpcf.Result -import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler -import org.opalj.fpcf.analyses.string_definition.preprocessing.FlatPathElement -import org.opalj.fpcf.analyses.string_definition.preprocessing.NestedPathElement -import org.opalj.fpcf.analyses.string_definition.preprocessing.Path -import org.opalj.fpcf.analyses.string_definition.preprocessing.SubPath -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.IntermediateEP -import org.opalj.fpcf.IntermediateResult -import org.opalj.fpcf.NoResult +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimLUBP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.fpcf.analyses.string_definition.preprocessing.WindowPathFinder import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.FPCFAnalysisScheduler +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder +import org.opalj.tac.fpcf.properties.TACAI /** * LocalStringDefinitionAnalysis processes a read operation of a local string variable at a program @@ -70,7 +72,7 @@ class LocalStringDefinitionAnalysis( cfg: CFG[Stmt[V], TACStmts[V]] ) - def analyze(data: P): PropertyComputationResult = { + def analyze(data: P): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lowerBound.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) @@ -113,8 +115,8 @@ class LocalStringDefinitionAnalysis( state = ComputationState(leanPaths, dependentVars, fpe2sci, cfg) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { - case FinalEP(e, p) ⇒ - return processFinalEP(data, dependees.values, state, e, p) + case FinalP(p) ⇒ + return processFinalP(data, dependees.values, state, ep.e, p) case _ ⇒ dependees.put(toAnalyze, ep) } @@ -131,7 +133,7 @@ class LocalStringDefinitionAnalysis( } if (dependees.nonEmpty) { - IntermediateResult( + InterimResult( data, StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, @@ -144,16 +146,16 @@ class LocalStringDefinitionAnalysis( } /** - * `processFinalEP` is responsible for handling the case that the `propertyStore` outputs a - * [[FinalEP]]. + * `processFinalP` is responsible for handling the case that the `propertyStore` outputs a + * [[FinalP]]. */ - private def processFinalEP( + private def processFinalP( data: P, dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState, e: Entity, p: Property - ): PropertyComputationResult = { + ): ProperPropertyComputationResult = { // Add mapping information (which will be used for computing the final result) val retrievedProperty = p.asInstanceOf[StringConstancyProperty] val currentSci = retrievedProperty.stringConstancyInformation @@ -167,7 +169,7 @@ class LocalStringDefinitionAnalysis( ).reduce(true) Result(data, StringConstancyProperty(finalSci)) } else { - IntermediateResult( + InterimResult( data, StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, @@ -190,15 +192,21 @@ class LocalStringDefinitionAnalysis( data: P, dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState - )(eps: SomeEPS): PropertyComputationResult = { - eps match { - case FinalEP(e, p) ⇒ - processFinalEP(data, dependees, state, e, p) - case IntermediateEP(_, lb, ub) ⇒ - IntermediateResult( + )(eps: SomeEPS): ProperPropertyComputationResult = { + val currentResult = eps match { + case FinalP(p) ⇒ + Some(processFinalP(data, dependees, state, eps.e, p)) + case InterimLUBP(lb, ub) ⇒ + Some(InterimResult( data, lb, ub, dependees, continuation(data, dependees, state) - ) - case _ ⇒ NoResult + )) + case _ ⇒ None + } + + if (currentResult.isDefined) { + currentResult.get + } else { + throw new IllegalStateException("Could not process the continuation successfully.") } } @@ -277,8 +285,8 @@ class LocalStringDefinitionAnalysis( ) wasTargetSeen = encounteredTarget currentDeps.foreach { nextPair ⇒ - val newExprs = InterpretationHandler.findNewOfVar(nextPair._1, stmts) - if (ignore != nextPair._1 && ignoreNews != newExprs) { + val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1, stmts) + if (ignore != nextPair._1 && ignoreNews != newExpressions) { dependees.put(nextPair._1, nextPair._2) } } @@ -289,21 +297,28 @@ class LocalStringDefinitionAnalysis( } -sealed trait LocalStringDefinitionAnalysisScheduler extends ComputationSpecification { +sealed trait LocalStringDefinitionAnalysisScheduler extends FPCFAnalysisScheduler { - final override def derives: Set[PropertyKind] = Set(StringConstancyProperty) + final override def uses: Set[PropertyBounds] = Set( + PropertyBounds.ub(TACAI), + PropertyBounds.ub(Callees), + PropertyBounds.lub(StringConstancyProperty) + ) - final override def uses: Set[PropertyKind] = { - Set() + final override type InitializationData = LocalStringDefinitionAnalysis + final override def init(p: SomeProject, ps: PropertyStore): InitializationData = { + new LocalStringDefinitionAnalysis(p) } - final override type InitializationData = Null + override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} - final def init(p: SomeProject, ps: PropertyStore): Null = null + override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} - def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} - - def afterPhaseCompletion(p: SomeProject, ps: PropertyStore): Unit = {} + override def afterPhaseCompletion( + p: SomeProject, + ps: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} } @@ -314,12 +329,14 @@ object LazyStringDefinitionAnalysis extends LocalStringDefinitionAnalysisScheduler with FPCFLazyAnalysisScheduler { - final override def startLazily( - p: SomeProject, ps: PropertyStore, unused: Null + override def register( + p: SomeProject, ps: PropertyStore, analysis: InitializationData ): FPCFAnalysis = { val analysis = new LocalStringDefinitionAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis } + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + } diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala similarity index 79% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 0254dabba2..58540e2528 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -1,11 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * @param cfg The control flow graph that underlies the instruction to interpret. @@ -15,8 +15,8 @@ import org.opalj.tac.TACStmts * @author Patrick Mell */ abstract class AbstractStringInterpreter( - protected val cfg: CFG[Stmt[V], TACStmts[V]], - protected val exprHandler: InterpretationHandler, + protected val cfg: CFG[Stmt[V], TACStmts[V]], + protected val exprHandler: InterpretationHandler ) { type T <: Any diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayInterpreter.scala similarity index 90% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayInterpreter.scala index 7d6092a326..ca1cf911b7 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/ArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayInterpreter.scala @@ -1,16 +1,17 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import scala.collection.mutable.ListBuffer + import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ArrayLoad import org.opalj.tac.ArrayStore +import org.opalj.tac.Assignment import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import scala.collection.mutable.ListBuffer - -import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.tac.Assignment +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `ArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala similarity index 88% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala index d5f751f303..33a39ba9ea 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.tac.TACStmts import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt -import org.opalj.tac.Stmt -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.BinaryExpr +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `BinaryExprInterpreter` is responsible for processing [[BinaryExpr]]ions. A list of currently diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FieldInterpreter.scala similarity index 78% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FieldInterpreter.scala index 386b357dd0..00e1d2f0cf 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/FieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FieldInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.tac.Stmt import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.GetField +import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `FieldInterpreter` is responsible for processing [[GetField]]s. Currently, there is only diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/GetStaticInterpreter.scala similarity index 78% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/GetStaticInterpreter.scala index 365d2260d4..a0fcea91c6 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/GetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/GetStaticInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.GetStatic import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `GetStaticInterpreter` is responsible for processing [[org.opalj.tac.GetStatic]]s. Currently, diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala similarity index 70% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala index 9da9e434ec..1aea7df7bb 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/IntegerValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.IntConst import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `IntegerValueInterpreter` is responsible for processing [[IntConst]]s. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala similarity index 96% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 96ebc04d46..ec1651eb42 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -1,15 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr @@ -26,6 +25,7 @@ import org.opalj.tac.StringConst import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * `InterpretationHandler` is responsible for processing expressions that are relevant in order to diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala similarity index 82% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala index 6b25c0766d..c095eb294e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NewInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala @@ -1,12 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.tac.TACStmts -import org.opalj.tac.Stmt -import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.New +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `NewInterpreter` is responsible for processing [[New]] expressions. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualFunctionCallInterpreter.scala similarity index 77% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualFunctionCallInterpreter.scala index 1d11e3895c..2a5bfe997e 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualFunctionCallInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `NonVirtualFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallInterpreter.scala similarity index 92% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallInterpreter.scala index 4ccaa861ab..eae77e81c4 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import scala.collection.mutable.ListBuffer import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts - -import scala.collection.mutable.ListBuffer +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `NonVirtualMethodCallInterpreter` is responsible for processing [[NonVirtualMethodCall]]s. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StaticFunctionCallInterpreter.scala similarity index 78% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StaticFunctionCallInterpreter.scala index 53ac9507c9..4dc69b6dcd 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StaticFunctionCallInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts -import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `StaticFunctionCallInterpreter` is responsible for processing [[StaticFunctionCall]]s. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala similarity index 75% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala index b873bf24c3..751da65a65 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/StringConstInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType -import org.opalj.tac.TACStmts +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.Stmt import org.opalj.tac.StringConst +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `StringConstInterpreter` is responsible for processing [[StringConst]]s. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualFunctionCallInterpreter.scala similarity index 95% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualFunctionCallInterpreter.scala index 2835524a51..121865b94a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualFunctionCallInterpreter.scala @@ -1,18 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.properties.StringConstancyProperty -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType import org.opalj.br.ObjectType +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `VirtualFunctionCallInterpreter` is responsible for processing [[VirtualFunctionCall]]s. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualMethodCallInterpreter.scala similarity index 81% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualMethodCallInterpreter.scala index 8fe03de6e0..fe072f3b01 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/interpretation/VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualMethodCallInterpreter.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringConstancyLevel -import org.opalj.fpcf.string_definition.properties.StringConstancyType +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `VirtualMethodCallInterpreter` is responsible for processing [[VirtualMethodCall]]s. diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala similarity index 99% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 6d90582558..b883422ae1 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -1,10 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.preprocessing +package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.BasicBlock import org.opalj.br.cfg.CatchNode import org.opalj.br.cfg.CFG @@ -15,6 +14,7 @@ import org.opalj.tac.ReturnValue import org.opalj.tac.Stmt import org.opalj.tac.Switch import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * [[AbstractPathFinder]] provides a scaffolding for finding all relevant paths in a CFG in the @@ -45,7 +45,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''e1''. */ protected case class HierarchicalCSOrder( - hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] ) /** @@ -662,8 +662,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -748,7 +748,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_) = Unit) + alreadySeen.foreach(seenNodes(_)= Unit) seenNodes(from) = Unit while (stack.nonEmpty) { diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala similarity index 94% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala index 91869aabb5..90d7738048 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/DefaultPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/DefaultPathFinder.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.preprocessing +package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing -import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * An approach based on an intuitive traversing of the control flow graph (CFG). This implementation diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala similarity index 98% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala index f44b1b3cb2..76512fe2e2 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/Path.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala @@ -1,11 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.preprocessing +package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler import org.opalj.value.ValueInformation import org.opalj.tac.Assignment import org.opalj.tac.DUVar @@ -13,6 +11,8 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.New import org.opalj.tac.Stmt import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * @author Patrick Mell diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala similarity index 86% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index ea17c9e2bd..a6012dcf6d 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -1,24 +1,24 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.preprocessing +package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing + +import scala.collection.mutable.ListBuffer import org.opalj.br.cfg.CFG -import org.opalj.fpcf.analyses.string_definition.V -import org.opalj.fpcf.analyses.string_definition.interpretation.InterpretationHandler -import org.opalj.fpcf.string_definition.properties.StringConstancyInformation -import org.opalj.fpcf.string_definition.properties.StringTree -import org.opalj.fpcf.string_definition.properties.StringTreeConcat -import org.opalj.fpcf.string_definition.properties.StringTreeCond -import org.opalj.fpcf.string_definition.properties.StringTreeConst -import org.opalj.fpcf.string_definition.properties.StringTreeOr -import org.opalj.fpcf.string_definition.properties.StringTreeRepetition +import org.opalj.br.fpcf.properties.properties.StringTree +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat +import org.opalj.br.fpcf.properties.string_definition.StringTreeCond +import org.opalj.br.fpcf.properties.string_definition.StringTreeConst +import org.opalj.br.fpcf.properties.string_definition.StringTreeOr +import org.opalj.br.fpcf.properties.string_definition.StringTreeRepetition import org.opalj.tac.Stmt import org.opalj.tac.TACStmts - -import scala.collection.mutable.ListBuffer +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * [[PathTransformer]] is responsible for transforming a [[Path]] into another representation, such - * as [[StringTree]]s for example. + * as [[org.opalj.br.fpcf.properties.properties.StringTree]]s for example. * An instance can handle several consecutive transformations of different paths as long as they * refer to the underlying control flow graph. If this is no longer the case, create a new instance * of this class with the corresponding (new) `cfg?`. @@ -114,14 +114,14 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * StringConstancyInformation need to be used that the [[InterpretationHandler]] * cannot infer / derive. For instance, if the exact value of an expression needs * to be determined by calling the - * [[org.opalj.fpcf.analyses.string_definition.LocalStringDefinitionAnalysis]] on - * another instance, store this information in fpe2Sci. + * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringDefinitionAnalysis]] + * on another instance, store this information in fpe2Sci. * @param resetExprHandler Whether to reset the underlying [[InterpretationHandler]] or not. * When calling this function from outside, the default value should do * fine in most of the cases. For further information, see * [[InterpretationHandler.reset]]. * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed - * [[StringTree]] will be returned. Note that all elements of the tree will be defined, + * [[org.opalj.br.fpcf.properties.properties.StringTree]] will be returned. Note that all elements of the tree will be defined, * i.e., if `path` contains sites that could not be processed (successfully), they will * not occur in the tree. */ diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala similarity index 96% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala index ef2daecba4..a20cda7b3a 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/preprocessing/WindowPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/WindowPathFinder.scala @@ -1,12 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses.string_definition.preprocessing +package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing -import org.opalj.fpcf.analyses.string_definition.V import org.opalj.br.cfg.CFG import org.opalj.tac.If import org.opalj.tac.Stmt import org.opalj.tac.Switch import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * An approach based on an intuitive traversing of the control flow graph (CFG). This implementation diff --git a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala similarity index 89% rename from OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index c2f86842a0..1d1eeca613 100644 --- a/OPAL/ai/src/main/scala/org/opalj/fpcf/analyses/string_definition/package.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -1,14 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.analyses +package org.opalj.tac.fpcf.analyses +import org.opalj.value.ValueInformation import org.opalj.br.Method import org.opalj.tac.DUVar -import org.opalj.value.ValueInformation /** * @author Patrick Mell */ -package object string_definition { +package object string_analysis { /** * The type of entities the [[LocalStringDefinitionAnalysis]] processes. From b8570be240f6e799cad81d7b86b2206cf9d6b6b8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 19:43:45 +0100 Subject: [PATCH 129/316] Made StringConstancyProperty extend Property. --- .../org/opalj/br/fpcf/properties/StringConstancyProperty.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index b73fc3f3ae..54326300cd 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -28,7 +28,7 @@ class StringConstancyProperty( } -object StringConstancyProperty extends StringConstancyPropertyMetaInformation { +object StringConstancyProperty extends Property with StringConstancyPropertyMetaInformation { final val PropertyKeyName = "StringConstancy" From d3a0e168c76efe6bf2db61a594219da4545531db Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 19:44:37 +0100 Subject: [PATCH 130/316] Further changes were required on the StringAnalysisReflectiveCalls file after having switched to the latest 'develop' version. --- .../info/StringAnalysisReflectiveCalls.scala | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index 65ba2db52d..2a06979263 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -8,7 +8,6 @@ import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.FinalE import org.opalj.fpcf.FinalP import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.BasicReport @@ -60,7 +59,7 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { * analysis and the second element corresponds to the method name in which the entity occurred, * i.e., a value in [[relevantMethodNames]]. */ - private val entities = ListBuffer[(P, String)]() + private val entityContext = ListBuffer[(P, String)]() /** * Stores all relevant method names of the Java Reflection API, i.e., those methods from the @@ -72,24 +71,25 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", "java.lang.Class#getField", "java.lang.Class#getDeclaredField", "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" - // The following is for the javax.crypto API - //"javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", - //"javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", - //"javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", - //"javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", - //"javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", - //"javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", - //"javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" + // The following is for the javax.crypto API + //"javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", + //"javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", + //"javax.crypto.CipherSpi#engineSetMode", "javax.crypto.CipherSpi#engineSetPadding", + //"javax.crypto.CipherSpi#engineUnwrap", "javax.crypto.EncryptedPrivateKeyInfo#getKeySpec", + //"javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", + //"javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", + //"javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" ) /** * A list of fully-qualified method names that are to be skipped, e.g., because they make the * analysis crash. */ - private val ignoreMethods = List( // For the next one, there should be a \w inside the second string - // "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform", - // Check this result: - //"com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize" + private val ignoreMethods = List( + // For the next one, there should be a \w inside the second string + // "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform", + // Check this result: + //"com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize" ) override def title: String = "String Analysis for Reflective Calls" @@ -156,26 +156,10 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { val duvar = call.params(index).asVar val e = (duvar, method) - ps(e, StringConstancyProperty.key) match { - case FinalE(prop: StringConstancyProperty) ⇒ - resultMap(call.name).append(prop.stringConstancyInformation) - case _ ⇒ entities.append( - (e, buildFQMethodName(call.declaringClass, call.name)) - ) - } - // Add all properties to the map; TODO: Add the following to end of the analysis - ps.waitOnPhaseCompletion() - while (entities.nonEmpty) { - val nextEntity = entities.head - ps.properties(nextEntity._1).toIndexedSeq.foreach { - case FinalP(prop: StringConstancyProperty) ⇒ - resultMap(nextEntity._2).append( - prop.stringConstancyInformation - ) - case _ ⇒ - } - entities.remove(0) - } + ps.force(e, StringConstancyProperty.key) + entityContext.append( + (e, buildFQMethodName(call.declaringClass, call.name)) + ) } } } @@ -247,6 +231,20 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { } } + // TODO: The call to waitOnPhaseCompletion is not 100 % correct, however, without it + // resultMap does not get filled at all + propertyStore.waitOnPhaseCompletion() + entityContext.foreach { + case (e, callName) ⇒ + propertyStore.properties(e).toIndexedSeq.foreach { + case FinalP(p) ⇒ + resultMap(callName).append( + p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + ) + case _ ⇒ + } + } + val t1 = System.currentTimeMillis() println(s"Elapsed Time: ${t1 - t0} ms") resultMapToReport(resultMap) From 50ee1539b53945660ba573fa3de30d88eacd473f Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 25 Jan 2019 19:45:15 +0100 Subject: [PATCH 131/316] Removed unnecessary comments and properly formatted the file. --- .../opalj/fpcf/LocalStringDefinitionTest.scala | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala index 2eb147a9b1..556ff3bc7d 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala @@ -29,16 +29,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V */ class LocalStringDefinitionTest extends PropertiesTest { - // val analyses: List[FPCFAnalysisScheduler] = List( - // RTACallGraphAnalysisScheduler, - // TriggeredStaticInitializerAnalysis, - // TriggeredInstantiatedTypesAnalysis, - // TriggeredLoadedClassesAnalysis, - // TACAITransformer, - // LazyCalleesAnalysis(Set(StandardInvokeCallees)), - // LazyStringDefinitionAnalysis - // ) - /** * @return Returns all relevant project files (NOT including library files) to run the tests. */ @@ -99,11 +89,8 @@ class LocalStringDefinitionTest extends PropertiesTest { val manager = p.get(FPCFAnalysesManagerKey) val (ps, _) = manager.runAll(LazyStringDefinitionAnalysis) - // val testContext = executeAnalyses(analyses) val testContext = TestContext(p, ps, List(new LocalStringDefinitionAnalysis(p))) - // val as = TestContext(p, ps, a :: testContext.analyses) - LazyStringDefinitionAnalysis.init(p, ps) LazyStringDefinitionAnalysis.schedule(ps, null) val tacProvider = p.get(DefaultTACAIKey) @@ -146,7 +133,8 @@ class LocalStringDefinitionTest extends PropertiesTest { object LocalStringDefinitionTest { - val fqStringDefAnnotation = "org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection" + val fqStringDefAnnotation = + "org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection" val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_definition.TestMethods" // The name of the method from which to extract DUVars to analyze val nameTestMethod = "analyzeString" From b7fe3edb5a52fe3e3f3848e1133df251e5999c5f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 11:04:45 +0100 Subject: [PATCH 132/316] Renamed classes, files, and packages (to match the same naming conventions). --- .../info/StringAnalysisReflectiveCalls.scala | 4 +- .../LocalTestMethods.java} | 14 +++--- .../StringConstancyLevel.java | 4 +- .../StringDefinitions.java | 11 ++--- .../StringDefinitionsCollection.java | 2 +- ...st.scala => LocalStringAnalysisTest.scala} | 43 ++++++++++--------- .../LocalStringAnalysisMatcher.scala} | 10 +++-- ...alysis.scala => LocalStringAnalysis.scala} | 27 ++++++------ .../preprocessing/PathTransformer.scala | 2 +- .../string_analysis/string_analysis.scala | 4 +- 10 files changed, 61 insertions(+), 60 deletions(-) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/{string_definition/TestMethods.java => string_analysis/LocalTestMethods.java} (98%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_definition => string_analysis}/StringConstancyLevel.java (76%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_definition => string_analysis}/StringDefinitions.java (78%) rename DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/{string_definition => string_analysis}/StringDefinitionsCollection.java (92%) rename DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/{LocalStringDefinitionTest.scala => LocalStringAnalysisTest.scala} (75%) rename DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/{string_definition/LocalStringDefinitionMatcher.scala => string_analysis/LocalStringAnalysisMatcher.scala} (90%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{LocalStringDefinitionAnalysis.scala => LocalStringAnalysis.scala} (94%) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index 2a06979263..f22dd4f793 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -30,7 +30,7 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall -import org.opalj.tac.fpcf.analyses.string_analysis.LazyStringDefinitionAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyLocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.P import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -196,7 +196,7 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { val t0 = System.currentTimeMillis() implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) - project.get(FPCFAnalysesManagerKey).runAll(LazyStringDefinitionAnalysis) + project.get(FPCFAnalysesManagerKey).runAll(LazyLocalStringAnalysis) val tacProvider = project.get(SimpleTACAIKey) // Stores the obtained results for each supported reflective operation diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java similarity index 98% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index 06b659bed8..48c76875b7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_definition/TestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -1,8 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.fixtures.string_definition; +package org.opalj.fpcf.fixtures.string_analysis; -import org.opalj.fpcf.properties.string_definition.StringDefinitions; -import org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection; +import org.opalj.fpcf.properties.string_analysis.StringDefinitions; +import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; import java.io.IOException; import java.lang.reflect.Field; @@ -12,10 +12,10 @@ import java.nio.file.Paths; import java.util.Random; -import static org.opalj.fpcf.properties.string_definition.StringConstancyLevel.*; +import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; /** - * This file contains various tests for the StringDefinitionAnalysis. The following things are to be + * This file contains various tests for the LocalStringAnalysis. The following things are to be * considered when adding test cases: *
      *
    • @@ -48,14 +48,14 @@ * * @author Patrick Mell */ -public class TestMethods { +public class LocalTestMethods { private String someStringField = ""; public static final String MY_CONSTANT = "mine"; /** * This method represents the test method which is serves as the trigger point for the - * {@link org.opalj.fpcf.LocalStringDefinitionTest} to know which string read operation to + * {@link org.opalj.fpcf.LocalStringAnalysisTest} to know which string read operation to * analyze. * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture * only one read operation. For how to get around this limitation, see the annotation. diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java similarity index 76% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java index 7cfa2716c6..8b3ac41cce 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringConstancyLevel.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringConstancyLevel.java @@ -1,9 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_definition; +package org.opalj.fpcf.properties.string_analysis; /** * Java annotations do not work with Scala enums, such as - * {@link org.opalj.br.fpcf.properties.string_definition.properties.StringConstancyLevel}. Thus, this enum. + * {@link org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel}. Thus, this enum. * * @author Patrick Mell */ diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java similarity index 78% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java index 035bdf9757..4e5776c212 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java @@ -1,13 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_definition; +package org.opalj.fpcf.properties.string_analysis; import org.opalj.fpcf.properties.PropertyValidator; import java.lang.annotation.*; /** - * The StringDefinitions annotation states how a string field or local variable is used during a - * program execution. + * The StringDefinitions annotation states how a string field or local variable looks like with + * respect to the possible string values that can be read as well as the constancy level, i.e., + * whether the string contains only constan or only dynamic parts or a mixture. *

      * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture * only one read operation per test method. If this is a limitation, either (1) duplicate the @@ -17,7 +18,7 @@ * * @author Patrick Mell */ -@PropertyValidator(key = "StringConstancy", validator = LocalStringDefinitionMatcher.class) +@PropertyValidator(key = "StringConstancy", validator = LocalStringAnalysisMatcher.class) @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.ANNOTATION_TYPE }) @@ -31,7 +32,7 @@ /** * A regexp like string that describes the element(s) that are expected. For the rules, refer to - * {@link org.opalj.br.fpcf.properties.string_definition.properties.StringTreeElement}. + * {@link org.opalj.fpcf.fixtures.string_analysis.LocalTestMethods}. * For example, "(* | (hello | world)^5)" describes a string which can 1) either be any string * or 2) a five time concatenation of "hello" and/or "world". */ diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java similarity index 92% rename from DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java rename to DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java index c05352d056..deadbc9349 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_definition/StringDefinitionsCollection.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitionsCollection.java @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_definition; +package org.opalj.fpcf.properties.string_analysis; import java.lang.annotation.*; diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringAnalysisTest.scala similarity index 75% rename from DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala rename to DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringAnalysisTest.scala index 556ff3bc7d..e3df449552 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringDefinitionTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringAnalysisTest.scala @@ -18,24 +18,25 @@ import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall -import org.opalj.tac.fpcf.analyses.string_analysis.LazyStringDefinitionAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringDefinitionAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyLocalStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * Tests whether the StringTrackingAnalysis works correctly. + * Tests whether the [[LocalStringAnalysis]] works correctly with respect to some well-defined + * tests. * * @author Patrick Mell */ -class LocalStringDefinitionTest extends PropertiesTest { +class LocalStringAnalysisTest extends PropertiesTest { /** * @return Returns all relevant project files (NOT including library files) to run the tests. */ private def getRelevantProjectFiles: Array[File] = { val necessaryFiles = Array( - "fixtures/string_definition/TestMethods.class", - "properties/string_definition/StringDefinitions.class" + "fixtures/string_analysis/LocalTestMethods.class", + "properties/string_analysis/StringDefinitions.class" ) val basePath = System.getProperty("user.dir")+ "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/" @@ -45,31 +46,31 @@ class LocalStringDefinitionTest extends PropertiesTest { /** * Extracts [[org.opalj.tac.UVar]]s from a set of statements. The locations of the UVars are - * identified by the argument to the very first call to TestMethods#analyzeString. + * identified by the argument to the very first call to LocalTestMethods#analyzeString. * * @param cfg The control flow graph from which to extract the UVar, usually derived from the - * method that contains the call(s) to TestMethods#analyzeString. - * @return Returns the arguments of the TestMethods#analyzeString as a DUVars list in the order - * in which they occurred in the given statements. + * method that contains the call(s) to LocalTestMethods#analyzeString. + * @return Returns the arguments of the LocalTestMethods#analyzeString as a DUVars list in the + * order in which they occurred in the given statements. */ private def extractUVars(cfg: CFG[Stmt[V], TACStmts[V]]): List[V] = { cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ - declClass.toJavaClass.getName == LocalStringDefinitionTest.fqTestMethodsClass && - name == LocalStringDefinitionTest.nameTestMethod + declClass.toJavaClass.getName == LocalStringAnalysisTest.fqTestMethodsClass && + name == LocalStringAnalysisTest.nameTestMethod case _ ⇒ false }.map(_.asVirtualMethodCall.params.head.asVar).toList } /** * Takes an annotation and checks if it is a - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]] annotation. + * [[org.opalj.fpcf.properties.string_analysis.StringDefinitions]] annotation. * * @param a The annotation to check. * @return True if the `a` is of type StringDefinitions and false otherwise. */ private def isStringUsageAnnotation(a: Annotation): Boolean = - a.annotationType.toJavaClass.getName == LocalStringDefinitionTest.fqStringDefAnnotation + a.annotationType.toJavaClass.getName == LocalStringAnalysisTest.fqStringDefAnnotation /** * Extracts a `StringDefinitions` annotation from a `StringDefinitionsCollection` annotation. @@ -88,11 +89,11 @@ class LocalStringDefinitionTest extends PropertiesTest { val p = Project(getRelevantProjectFiles, Array[File]()) val manager = p.get(FPCFAnalysesManagerKey) - val (ps, _) = manager.runAll(LazyStringDefinitionAnalysis) - val testContext = TestContext(p, ps, List(new LocalStringDefinitionAnalysis(p))) + val (ps, _) = manager.runAll(LazyLocalStringAnalysis) + val testContext = TestContext(p, ps, List(new LocalStringAnalysis(p))) - LazyStringDefinitionAnalysis.init(p, ps) - LazyStringDefinitionAnalysis.schedule(ps, null) + LazyLocalStringAnalysis.init(p, ps) + LazyLocalStringAnalysis.schedule(ps, null) val tacProvider = p.get(DefaultTACAIKey) // We need a "method to entity" matching for the evaluation (see further below) @@ -131,11 +132,11 @@ class LocalStringDefinitionTest extends PropertiesTest { } -object LocalStringDefinitionTest { +object LocalStringAnalysisTest { val fqStringDefAnnotation = - "org.opalj.fpcf.properties.string_definition.StringDefinitionsCollection" - val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_definition.TestMethods" + "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" + val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.LocalTestMethods" // The name of the method from which to extract DUVars to analyze val nameTestMethod = "analyzeString" diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/LocalStringAnalysisMatcher.scala similarity index 90% rename from DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala rename to DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/LocalStringAnalysisMatcher.scala index fd22239447..abda6c47f4 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_definition/LocalStringDefinitionMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/LocalStringAnalysisMatcher.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.fpcf.properties.string_definition +package org.opalj.fpcf.properties.string_analysis import org.opalj.br.analyses.Project import org.opalj.br.AnnotationLike @@ -14,11 +14,12 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty * * @author Patrick Mell */ -class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { +class LocalStringAnalysisMatcher extends AbstractPropertyMatcher { /** * @param a An annotation like of type - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * [[org.opalj.fpcf.properties.string_analysis.StringDefinitions]]. + * * @return Returns the constancy level specified in the annotation as a string. In case an * annotation other than StringDefinitions is passed, an [[IllegalArgumentException]] * will be thrown (since it cannot be processed). @@ -34,7 +35,8 @@ class LocalStringDefinitionMatcher extends AbstractPropertyMatcher { /** * @param a An annotation like of type - * [[org.opalj.fpcf.properties.string_definition.StringDefinitions]]. + * [[org.opalj.fpcf.properties.string_analysis.StringDefinitions]]. + * * @return Returns the ''expectedStrings'' value from the annotation. In case an annotation * other than StringDefinitions is passed, an [[IllegalArgumentException]] will be * thrown (since it cannot be processed). diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringDefinitionAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala similarity index 94% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringDefinitionAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala index ff6980dfea..42926382fb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringDefinitionAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala @@ -39,20 +39,19 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinde import org.opalj.tac.fpcf.properties.TACAI /** - * LocalStringDefinitionAnalysis processes a read operation of a local string variable at a program + * LocalStringAnalysis processes a read operation of a local string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. * - * "Local" as this analysis takes into account only the enclosing function as a context. Values - * coming from other functions are regarded as dynamic values even if the function returns a - * constant string value. [[StringConstancyProperty]] models this by inserting "*" into the set of - * possible strings. + * "Local" as this analysis takes into account only the enclosing function as a context, i.e., it + * intraprocedural. Values coming from other functions are regarded as dynamic values even if the + * function returns a constant string value. * - * StringConstancyProperty might contain more than one possible string, e.g., if the source of the - * value is an array. + * The StringConstancyProperty might contain more than one possible string, e.g., if the source of + * the value is an array. * * @author Patrick Mell */ -class LocalStringDefinitionAnalysis( +class LocalStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { @@ -297,7 +296,7 @@ class LocalStringDefinitionAnalysis( } -sealed trait LocalStringDefinitionAnalysisScheduler extends FPCFAnalysisScheduler { +sealed trait LocalStringAnalysisScheduler extends FPCFAnalysisScheduler { final override def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), @@ -305,9 +304,9 @@ sealed trait LocalStringDefinitionAnalysisScheduler extends FPCFAnalysisSchedule PropertyBounds.lub(StringConstancyProperty) ) - final override type InitializationData = LocalStringDefinitionAnalysis + final override type InitializationData = LocalStringAnalysis final override def init(p: SomeProject, ps: PropertyStore): InitializationData = { - new LocalStringDefinitionAnalysis(p) + new LocalStringAnalysis(p) } override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -325,14 +324,12 @@ sealed trait LocalStringDefinitionAnalysisScheduler extends FPCFAnalysisSchedule /** * Executor for the lazy analysis. */ -object LazyStringDefinitionAnalysis - extends LocalStringDefinitionAnalysisScheduler - with FPCFLazyAnalysisScheduler { +object LazyLocalStringAnalysis extends LocalStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData ): FPCFAnalysis = { - val analysis = new LocalStringDefinitionAnalysis(p) + val analysis = new LocalStringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index a6012dcf6d..34699e1812 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -114,7 +114,7 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * StringConstancyInformation need to be used that the [[InterpretationHandler]] * cannot infer / derive. For instance, if the exact value of an expression needs * to be determined by calling the - * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringDefinitionAnalysis]] + * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis]] * on another instance, store this information in fpe2Sci. * @param resetExprHandler Whether to reset the underlying [[InterpretationHandler]] or not. * When calling this function from outside, the default value should do diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index 1d1eeca613..aea1caf9e4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -11,14 +11,14 @@ import org.opalj.tac.DUVar package object string_analysis { /** - * The type of entities the [[LocalStringDefinitionAnalysis]] processes. + * The type of entities the [[LocalStringAnalysis]] processes. * * @note The analysis requires further context information, see [[P]]. */ type V = DUVar[ValueInformation] /** - * [[LocalStringDefinitionAnalysis]] processes a local variable within the context of a + * [[LocalStringAnalysis]] processes a local variable within the context of a * particular context, i.e., the method in which it is used. */ type P = (V, Method) From a74f252c8a7ea476e70f1551ff8f44d70281e51a Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 15:45:31 +0100 Subject: [PATCH 133/316] Simplified the code. --- .../string_analysis/LocalStringAnalysis.scala | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala index 42926382fb..5b2bd41902 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala @@ -191,22 +191,12 @@ class LocalStringAnalysis( data: P, dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState - )(eps: SomeEPS): ProperPropertyComputationResult = { - val currentResult = eps match { - case FinalP(p) ⇒ - Some(processFinalP(data, dependees, state, eps.e, p)) - case InterimLUBP(lb, ub) ⇒ - Some(InterimResult( - data, lb, ub, dependees, continuation(data, dependees, state) - )) - case _ ⇒ None - } - - if (currentResult.isDefined) { - currentResult.get - } else { - throw new IllegalStateException("Could not process the continuation successfully.") - } + )(eps: SomeEPS): ProperPropertyComputationResult = eps match { + case FinalP(p) ⇒ processFinalP(data, dependees, state, eps.e, p) + case InterimLUBP(lb, ub) ⇒ InterimResult( + data, lb, ub, dependees, continuation(data, dependees, state) + ) + case _ ⇒ throw new IllegalStateException("Could not process the continuation successfully.") } /** From 8a8ee87bb068978df1585f75d2eb55103cd1764f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 16:33:44 +0100 Subject: [PATCH 134/316] Generalized the StringAnalysisTest. --- ...sisTest.scala => StringAnalysisTest.scala} | 91 +++++++++++++------ 1 file changed, 63 insertions(+), 28 deletions(-) rename DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/{LocalStringAnalysisTest.scala => StringAnalysisTest.scala} (72%) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala similarity index 72% rename from DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringAnalysisTest.scala rename to DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index e3df449552..f2c68e3aea 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/LocalStringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -3,10 +3,12 @@ package org.opalj package fpcf import java.io.File +import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.collection.immutable.ConstArray import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method @@ -23,21 +25,27 @@ import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * Tests whether the [[LocalStringAnalysis]] works correctly with respect to some well-defined - * tests. - * - * @author Patrick Mell + * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. + * @param nameTestMethod The name of the method from which to extract DUVars to analyze. + * @param filesToLoad Necessary (test) files / classes to load. Note that this list should not + * include "StringDefinitions.class" as this class is loaded by default. */ -class LocalStringAnalysisTest extends PropertiesTest { +sealed class StringAnalysisTestRunner( + val fqTestMethodsClass: String, + val nameTestMethod: String, + val filesToLoad: List[String] +) extends PropertiesTest { + + private val fqStringDefAnnotation = + "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" /** * @return Returns all relevant project files (NOT including library files) to run the tests. */ - private def getRelevantProjectFiles: Array[File] = { + def getRelevantProjectFiles: Array[File] = { val necessaryFiles = Array( - "fixtures/string_analysis/LocalTestMethods.class", "properties/string_analysis/StringDefinitions.class" - ) + ) ++ filesToLoad val basePath = System.getProperty("user.dir")+ "/DEVELOPING_OPAL/validate/target/scala-2.12/test-classes/org/opalj/fpcf/" @@ -53,11 +61,10 @@ class LocalStringAnalysisTest extends PropertiesTest { * @return Returns the arguments of the LocalTestMethods#analyzeString as a DUVars list in the * order in which they occurred in the given statements. */ - private def extractUVars(cfg: CFG[Stmt[V], TACStmts[V]]): List[V] = { + def extractUVars(cfg: CFG[Stmt[V], TACStmts[V]]): List[V] = { cfg.code.instructions.filter { case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ - declClass.toJavaClass.getName == LocalStringAnalysisTest.fqTestMethodsClass && - name == LocalStringAnalysisTest.nameTestMethod + declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod case _ ⇒ false }.map(_.asVirtualMethodCall.params.head.asVar).toList } @@ -69,8 +76,8 @@ class LocalStringAnalysisTest extends PropertiesTest { * @param a The annotation to check. * @return True if the `a` is of type StringDefinitions and false otherwise. */ - private def isStringUsageAnnotation(a: Annotation): Boolean = - a.annotationType.toJavaClass.getName == LocalStringAnalysisTest.fqStringDefAnnotation + def isStringUsageAnnotation(a: Annotation): Boolean = + a.annotationType.toJavaClass.getName == fqStringDefAnnotation /** * Extracts a `StringDefinitions` annotation from a `StringDefinitionsCollection` annotation. @@ -82,24 +89,19 @@ class LocalStringAnalysisTest extends PropertiesTest { * get. * @return Returns the desired `StringDefinitions` annotation. */ - private def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = + def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation - describe("the org.opalj.fpcf.StringTrackingAnalysis is started") { - val p = Project(getRelevantProjectFiles, Array[File]()) - - val manager = p.get(FPCFAnalysesManagerKey) - val (ps, _) = manager.runAll(LazyLocalStringAnalysis) - val testContext = TestContext(p, ps, List(new LocalStringAnalysis(p))) - - LazyLocalStringAnalysis.init(p, ps) - LazyLocalStringAnalysis.schedule(ps, null) - val tacProvider = p.get(DefaultTACAIKey) - + def determineEAS( + p: Project[URL], + ps: PropertyStore, + allMethodsWithBody: ConstArray[Method], + ): Traversable[((V, Method), String ⇒ String, List[Annotation])] = { // We need a "method to entity" matching for the evaluation (see further below) val m2e = mutable.HashMap[Method, Entity]() - p.allMethodsWithBody.filter { + val tacProvider = p.get(DefaultTACAIKey) + allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || isStringUsageAnnotation(a) ) @@ -125,6 +127,37 @@ class LocalStringAnalysisTest extends PropertiesTest { ) } } + + eas + } + +} + +/** + * Tests whether the [[LocalStringAnalysis]] works correctly with respect to some well-defined + * tests. + * + * @author Patrick Mell + */ +class LocalStringAnalysisTest extends PropertiesTest { + + describe("the org.opalj.fpcf.LocalStringAnalysis is started") { + val runner = new StringAnalysisTestRunner( + LocalStringAnalysisTest.fqTestMethodsClass, + LocalStringAnalysisTest.nameTestMethod, + LocalStringAnalysisTest.filesToLoad + ) + val p = Project(runner.getRelevantProjectFiles, Array[File]()) + + val manager = p.get(FPCFAnalysesManagerKey) + val (ps, _) = manager.runAll(LazyLocalStringAnalysis) + val testContext = TestContext(p, ps, List(new LocalStringAnalysis(p))) + + LazyLocalStringAnalysis.init(p, ps) + LazyLocalStringAnalysis.schedule(ps, null) + + val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) + testContext.propertyStore.shutdown() validateProperties(testContext, eas, Set("StringConstancy")) ps.waitOnPhaseCompletion() @@ -134,10 +167,12 @@ class LocalStringAnalysisTest extends PropertiesTest { object LocalStringAnalysisTest { - val fqStringDefAnnotation = - "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.LocalTestMethods" // The name of the method from which to extract DUVars to analyze val nameTestMethod = "analyzeString" + // Files to load for the runner + val filesToLoad = List( + "fixtures/string_analysis/LocalTestMethods.class" + ) } From fc68364cfad191429dba0fcbcf69a4db47e5b44f Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 16:45:21 +0100 Subject: [PATCH 135/316] Added a InterproceduralStringAnalysis (currently not interprocedural) and extended the test suite accordingly. --- .../InterproceduralTestMethods.java | 56 +++ .../org/opalj/fpcf/StringAnalysisTest.scala | 46 +++ .../InterproceduralStringAnalysis.scala | 326 ++++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java new file mode 100644 index 0000000000..6acd245a8b --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -0,0 +1,56 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +import org.opalj.fpcf.properties.string_analysis.StringDefinitions; +import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; + +import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.DYNAMIC; + +/** + * This file contains various tests for the InterproceduralStringAnalysis. For further information + * on what to consider, please see {@link LocalTestMethods} + * + * @author Patrick Mell + */ +public class InterproceduralTestMethods { + + private String someStringField = ""; + public static final String MY_CONSTANT = "mine"; + + /** + * This method represents the test method which is serves as the trigger point for the + * {@link org.opalj.fpcf.LocalStringAnalysisTest} to know which string read operation to + * analyze. + * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture + * only one read operation. For how to get around this limitation, see the annotation. + * + * @param s Some string which is to be analyzed. + */ + public void analyzeString(String s) { + } + + @StringDefinitionsCollection( + value = "at this point, function call cannot be handled => DYNAMIC", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, expectedStrings = "\\w" + ) + }) + public void fromFunctionCall() { + String className = getStringBuilderClassName(); + analyzeString(className); + } + + private String getRuntimeClassName() { + return "java.lang.Runtime"; + } + + private String getStringBuilderClassName() { + return "java.lang.StringBuilder"; + } + + private String getSimpleStringBuilderClassName() { + return "StringBuilder"; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index f2c68e3aea..c762352526 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -20,6 +20,8 @@ import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyLocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -176,3 +178,47 @@ object LocalStringAnalysisTest { ) } + +/** + * Tests whether the [[LocalStringAnalysis]] works correctly with respect to some well-defined + * tests. + * + * @author Patrick Mell + */ +class InterproceduralStringAnalysisTest extends PropertiesTest { + + describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { + val runner = new StringAnalysisTestRunner( + InterproceduralStringAnalysisTest.fqTestMethodsClass, + InterproceduralStringAnalysisTest.nameTestMethod, + InterproceduralStringAnalysisTest.filesToLoad + ) + val p = Project(runner.getRelevantProjectFiles, Array[File]()) + + val manager = p.get(FPCFAnalysesManagerKey) + val (ps, _) = manager.runAll(LazyInterproceduralStringAnalysis) + val testContext = TestContext(p, ps, List(new InterproceduralStringAnalysis(p))) + + LazyInterproceduralStringAnalysis.init(p, ps) + LazyInterproceduralStringAnalysis.schedule(ps, null) + + val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) + + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set("StringConstancy")) + ps.waitOnPhaseCompletion() + } + +} + +object InterproceduralStringAnalysisTest { + + val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.InterproceduralTestMethods" + // The name of the method from which to extract DUVars to analyze + val nameTestMethod = "analyzeString" + // Files to load for the runner + val filesToLoad = List( + "fixtures/string_analysis/InterproceduralTestMethods.class" + ) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala new file mode 100644 index 0000000000..3956d1d3ec --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -0,0 +1,326 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimLUBP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEPS +import org.opalj.br.analyses.SomeProject +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.FPCFAnalysisScheduler +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.ExprStmt +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder +import org.opalj.tac.fpcf.properties.TACAI + +/** + * InterproceduralStringAnalysis processes a read operation of a string variable at a program + * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. + * + * In comparison to [[LocalStringAnalysis]], this version tries to resolve method calls that are + * involved in a string construction as far as possible. + * + * @author Patrick Mell + */ +class InterproceduralStringAnalysis( + val project: SomeProject +) extends FPCFAnalysis { + + /** + * This class is to be used to store state information that are required at a later point in + * time during the analysis, e.g., due to the fact that another analysis had to be triggered to + * have all required information ready for a final result. + */ + private case class ComputationState( + // The lean path that was computed + computedLeanPath: Path, + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + var2IndexMapping: mutable.Map[V, Int], + // A mapping from values of FlatPathElements to StringConstancyInformation + fpe2sci: mutable.Map[Int, StringConstancyInformation], + // The control flow graph on which the computedLeanPath is based + cfg: CFG[Stmt[V], TACStmts[V]] + ) + + def analyze(data: P): ProperPropertyComputationResult = { + // sci stores the final StringConstancyInformation (if it can be determined now at all) + var sci = StringConstancyProperty.lowerBound.stringConstancyInformation + val tacProvider = p.get(SimpleTACAIKey) + val cfg = tacProvider(data._2).cfg + val stmts = cfg.code.instructions + + val uvar = data._1 + val defSites = uvar.definedBy.toArray.sorted + // Function parameters are currently regarded as dynamic value; the following if finds read + // operations of strings (not String{Builder, Buffer}s, they will be handles further down + if (defSites.head < 0) { + return Result(data, StringConstancyProperty.lowerBound) + } + val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) + + // If not empty, this very routine can only produce an intermediate result + val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() + // state will be set to a non-null value if this analysis needs to call other analyses / + // itself; only in the case it calls itself, will state be used, thus, it is valid to + // initialize it with null + var state: ComputationState = null + + val call = stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) + // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated + if (initDefSites.isEmpty) { + return Result(data, StringConstancyProperty.lowerBound) + } + + val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) + val leanPaths = paths.makeLeanPath(uvar, stmts) + + // Find DUVars, that the analysis of the current entity depends on + val dependentVars = findDependentVars(leanPaths, stmts, uvar) + if (dependentVars.nonEmpty) { + dependentVars.keys.foreach { nextVar ⇒ + val toAnalyze = (nextVar, data._2) + val fpe2sci = mutable.Map[Int, StringConstancyInformation]() + state = ComputationState(leanPaths, dependentVars, fpe2sci, cfg) + val ep = propertyStore(toAnalyze, StringConstancyProperty.key) + ep match { + case FinalP(p) ⇒ + return processFinalP(data, dependees.values, state, ep.e, p) + case _ ⇒ + dependees.put(toAnalyze, ep) + } + } + } else { + sci = new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true) + } + } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings + else { + val interHandler = InterpretationHandler(cfg) + sci = StringConstancyInformation.reduceMultiple( + uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList + ) + } + + if (dependees.nonEmpty) { + InterimResult( + data, + StringConstancyProperty.upperBound, + StringConstancyProperty.lowerBound, + dependees.values, + continuation(data, dependees.values, state) + ) + } else { + Result(data, StringConstancyProperty(sci)) + } + } + + /** + * `processFinalP` is responsible for handling the case that the `propertyStore` outputs a + * [[FinalP]]. + */ + private def processFinalP( + data: P, + dependees: Iterable[EOptionP[Entity, Property]], + state: ComputationState, + e: Entity, + p: Property + ): ProperPropertyComputationResult = { + // Add mapping information (which will be used for computing the final result) + val retrievedProperty = p.asInstanceOf[StringConstancyProperty] + val currentSci = retrievedProperty.stringConstancyInformation + state.fpe2sci.put(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) + + // No more dependees => Return the result for this analysis run + val remDependees = dependees.filter(_.e != e) + if (remDependees.isEmpty) { + val finalSci = new PathTransformer(state.cfg).pathToStringTree( + state.computedLeanPath, state.fpe2sci.toMap + ).reduce(true) + Result(data, StringConstancyProperty(finalSci)) + } else { + InterimResult( + data, + StringConstancyProperty.upperBound, + StringConstancyProperty.lowerBound, + remDependees, + continuation(data, remDependees, state) + ) + } + } + + /** + * Continuation function. + * + * @param data The data that was passed to the `analyze` function. + * @param dependees A list of dependencies that this analysis run depends on. + * @param state The computation state (which was originally captured by `analyze` and possibly + * extended / updated by other methods involved in computing the final result. + * @return This function can either produce a final result or another intermediate result. + */ + private def continuation( + data: P, + dependees: Iterable[EOptionP[Entity, Property]], + state: ComputationState + )(eps: SomeEPS): ProperPropertyComputationResult = eps match { + case FinalP(p) ⇒ processFinalP(data, dependees, state, eps.e, p) + case InterimLUBP(lb, ub) ⇒ InterimResult( + data, lb, ub, dependees, continuation(data, dependees, state) + ) + case _ ⇒ throw new IllegalStateException("Could not process the continuation successfully.") + } + + /** + * Helper / accumulator function for finding dependees. For how dependees are detected, see + * [[findDependentVars]]. Returns a list of pairs of DUVar and the index of the + * [[FlatPathElement.element]] in which it occurs. + */ + private def findDependeesAcc( + subpath: SubPath, + stmts: Array[Stmt[V]], + target: V, + foundDependees: ListBuffer[(V, Int)], + hasTargetBeenSeen: Boolean + ): (ListBuffer[(V, Int)], Boolean) = { + var encounteredTarget = false + subpath match { + case fpe: FlatPathElement ⇒ + if (target.definedBy.contains(fpe.element)) { + encounteredTarget = true + } + // For FlatPathElements, search for DUVars on which the toString method is called + // and where these toString calls are the parameter of an append call + stmts(fpe.element) match { + case ExprStmt(_, outerExpr) ⇒ + if (InterpretationHandler.isStringBuilderBufferAppendCall(outerExpr)) { + val param = outerExpr.asVirtualFunctionCall.params.head.asVar + param.definedBy.filter(_ >= 0).foreach { ds ⇒ + val expr = stmts(ds).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(expr)) { + foundDependees.append(( + outerExpr.asVirtualFunctionCall.params.head.asVar, + fpe.element + )) + } + } + } + case _ ⇒ + } + (foundDependees, encounteredTarget) + case npe: NestedPathElement ⇒ + npe.element.foreach { nextSubpath ⇒ + if (!encounteredTarget) { + val (_, seen) = findDependeesAcc( + nextSubpath, stmts, target, foundDependees, encounteredTarget + ) + encounteredTarget = seen + } + } + (foundDependees, encounteredTarget) + case _ ⇒ (foundDependees, encounteredTarget) + } + } + + /** + * Takes a `path`, this should be the lean path of a [[Path]], as well as a context in the form + * of statements, `stmts`, and detects all dependees within `path`. Dependees are found by + * looking at all elements in the path, and check whether the argument of an `append` call is a + * value that stems from a `toString` call of a [[StringBuilder]] or [[StringBuffer]]. This + * function then returns the found UVars along with the indices of those append statements. + * + * @note In order to make sure that a [[org.opalj.tac.DUVar]] does not depend on itself, pass + * this variable as `ignore`. + */ + private def findDependentVars( + path: Path, stmts: Array[Stmt[V]], ignore: V + ): mutable.LinkedHashMap[V, Int] = { + val dependees = mutable.LinkedHashMap[V, Int]() + val ignoreNews = InterpretationHandler.findNewOfVar(ignore, stmts) + var wasTargetSeen = false + + path.elements.foreach { nextSubpath ⇒ + if (!wasTargetSeen) { + val (currentDeps, encounteredTarget) = findDependeesAcc( + nextSubpath, stmts, ignore, ListBuffer(), hasTargetBeenSeen = false + ) + wasTargetSeen = encounteredTarget + currentDeps.foreach { nextPair ⇒ + val newExpressions = InterpretationHandler.findNewOfVar(nextPair._1, stmts) + if (ignore != nextPair._1 && ignoreNews != newExpressions) { + dependees.put(nextPair._1, nextPair._2) + } + } + } + } + dependees + } + +} + +sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { + + final override def uses: Set[PropertyBounds] = Set( + PropertyBounds.ub(TACAI), + PropertyBounds.ub(Callees), + PropertyBounds.lub(StringConstancyProperty) + ) + + final override type InitializationData = InterproceduralStringAnalysis + final override def init(p: SomeProject, ps: PropertyStore): InitializationData = { + new InterproceduralStringAnalysis(p) + } + + override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} + + override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = {} + + override def afterPhaseCompletion( + p: SomeProject, + ps: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} + +} + +/** + * Executor for the lazy analysis. + */ +object LazyInterproceduralStringAnalysis + extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + + override def register( + p: SomeProject, ps: PropertyStore, analysis: InitializationData + ): FPCFAnalysis = { + val analysis = new InterproceduralStringAnalysis(p) + ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) + analysis + } + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + +} From acdee4b47062f48ae3e2d6be4987ef56dcf97029 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 26 Jan 2019 16:47:43 +0100 Subject: [PATCH 136/316] Renamed the LocalStringAnalysisMatcher file. --- .../fpcf/properties/string_analysis/StringDefinitions.java | 2 +- ...lStringAnalysisMatcher.scala => StringAnalysisMatcher.scala} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/{LocalStringAnalysisMatcher.scala => StringAnalysisMatcher.scala} (97%) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java index 4e5776c212..d8512e51ce 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/string_analysis/StringDefinitions.java @@ -18,7 +18,7 @@ * * @author Patrick Mell */ -@PropertyValidator(key = "StringConstancy", validator = LocalStringAnalysisMatcher.class) +@PropertyValidator(key = "StringConstancy", validator = StringAnalysisMatcher.class) @Documented @Retention(RetentionPolicy.CLASS) @Target({ ElementType.ANNOTATION_TYPE }) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/LocalStringAnalysisMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala similarity index 97% rename from DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/LocalStringAnalysisMatcher.scala rename to DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala index abda6c47f4..aa5515d54f 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/LocalStringAnalysisMatcher.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/string_analysis/StringAnalysisMatcher.scala @@ -14,7 +14,7 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty * * @author Patrick Mell */ -class LocalStringAnalysisMatcher extends AbstractPropertyMatcher { +class StringAnalysisMatcher extends AbstractPropertyMatcher { /** * @param a An annotation like of type From 1a1ae06ece5a576379b0338e9b0d0db42f143bdb Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 28 Jan 2019 16:48:00 +0100 Subject: [PATCH 137/316] Extended the test suite by analyses that are required to use the call graph. --- .../org/opalj/fpcf/StringAnalysisTest.scala | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index c762352526..be8b534e07 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -16,15 +16,31 @@ import org.opalj.br.cfg.CFG import org.opalj.br.Annotations import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.fpcf.cg.properties.ReflectionRelatedCallees +import org.opalj.br.fpcf.cg.properties.SerializationRelatedCallees +import org.opalj.br.fpcf.cg.properties.StandardInvokeCallees +import org.opalj.br.fpcf.cg.properties.ThreadRelatedIncompleteCallSites +import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.cg.reflection.TriggeredReflectionRelatedCallsAnalysis +import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler +import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler +import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyLocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis +import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredStaticInitializerAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredThreadRelatedCallsAnalysis +import org.opalj.tac.fpcf.analyses.TACAITransformer +import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -196,14 +212,34 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { val p = Project(runner.getRelevantProjectFiles, Array[File]()) val manager = p.get(FPCFAnalysesManagerKey) - val (ps, _) = manager.runAll(LazyInterproceduralStringAnalysis) - val testContext = TestContext(p, ps, List(new InterproceduralStringAnalysis(p))) + val (ps, analyses) = manager.runAll( + TACAITransformer, + LazyL0BaseAIAnalysis, + RTACallGraphAnalysisScheduler, + TriggeredStaticInitializerAnalysis, + TriggeredLoadedClassesAnalysis, + TriggeredFinalizerAnalysisScheduler, + TriggeredThreadRelatedCallsAnalysis, + TriggeredSerializationRelatedCallsAnalysis, + TriggeredReflectionRelatedCallsAnalysis, + TriggeredSystemPropertiesAnalysis, + TriggeredInstantiatedTypesAnalysis, + LazyCalleesAnalysis(Set( + StandardInvokeCallees, + SerializationRelatedCallees, + ReflectionRelatedCallees, + ThreadRelatedIncompleteCallSites + )), + LazyInterproceduralStringAnalysis + ) + val testContext = TestContext( + p, ps, List(new InterproceduralStringAnalysis(p)) ++ analyses.map(_._2) + ) LazyInterproceduralStringAnalysis.init(p, ps) LazyInterproceduralStringAnalysis.schedule(ps, null) val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) - testContext.propertyStore.shutdown() validateProperties(testContext, eas, Set("StringConstancy")) ps.waitOnPhaseCompletion() From d5edeec8f76c3de4787201ccdef8130d107645e5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 28 Jan 2019 19:46:48 +0100 Subject: [PATCH 138/316] 1) Split the InterpretationHandler into a class hierarchy with two sub-classes (for inter- and intraprocedural processing) 2) Split the logic for some interpreters to separately support inter- and intraprocedural processing --- .../InterproceduralStringAnalysis.scala | 73 +++++-- .../string_analysis/LocalStringAnalysis.scala | 3 +- .../AbstractStringInterpreter.scala | 5 + .../BinaryExprInterpreter.scala | 4 +- .../IntegerValueInterpreter.scala | 2 + .../InterpretationHandler.scala | 98 ++------- .../InterproceduralArrayInterpreter.scala | 76 +++++++ .../InterproceduralFieldInterpreter.scala | 46 +++++ ...InterproceduralInterpretationHandler.scala | 103 +++++++++ ...ralNonVirtualFunctionCallInterpreter.scala | 45 ++++ ...duralNonVirtualMethodCallInterpreter.scala | 71 +++++++ ...ceduralStaticFunctionCallInterpreter.scala | 47 +++++ ...eduralVirtualFunctionCallInterpreter.scala | 195 ++++++++++++++++++ ...oceduralVirtualMethodCallInterpreter.scala | 55 +++++ ... => IntraproceduralArrayInterpreter.scala} | 8 +- ... => IntraproceduralFieldInterpreter.scala} | 10 +- ...IntraproceduralGetStaticInterpreter.scala} | 10 +- ...IntraproceduralInterpretationHandler.scala | 94 +++++++++ ...alNonVirtualFunctionCallInterpreter.scala} | 8 +- ...uralNonVirtualMethodCallInterpreter.scala} | 10 +- ...eduralStaticFunctionCallInterpreter.scala} | 14 +- ...duralVirtualFunctionCallInterpreter.scala} | 12 +- ...ceduralVirtualMethodCallInterpreter.scala} | 7 +- .../interpretation/NewInterpreter.scala | 2 + .../StringConstInterpreter.scala | 3 + .../preprocessing/PathTransformer.scala | 25 +-- 26 files changed, 880 insertions(+), 146 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ArrayInterpreter.scala => IntraproceduralArrayInterpreter.scala} (90%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{FieldInterpreter.scala => IntraproceduralFieldInterpreter.scala} (77%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{GetStaticInterpreter.scala => IntraproceduralGetStaticInterpreter.scala} (79%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{NonVirtualFunctionCallInterpreter.scala => IntraproceduralNonVirtualFunctionCallInterpreter.scala} (83%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{NonVirtualMethodCallInterpreter.scala => IntraproceduralNonVirtualMethodCallInterpreter.scala} (89%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{StaticFunctionCallInterpreter.scala => IntraproceduralStaticFunctionCallInterpreter.scala} (71%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{VirtualFunctionCallInterpreter.scala => IntraproceduralVirtualFunctionCallInterpreter.scala} (95%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{VirtualMethodCallInterpreter.scala => IntraproceduralVirtualMethodCallInterpreter.scala} (87%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 3956d1d3ec..5a59015d88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -15,6 +15,7 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.FPCFAnalysis @@ -23,20 +24,22 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.ExprStmt -import org.opalj.tac.SimpleTACAIKey +import org.opalj.br.DeclaredMethod import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder +import org.opalj.tac.ExprStmt +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -68,6 +71,28 @@ class InterproceduralStringAnalysis( ) def analyze(data: P): ProperPropertyComputationResult = { + val declaredMethods = project.get(DeclaredMethodsKey) + // TODO: Is there a way to get the declared method in constant time? + val dm = declaredMethods.declaredMethods.find(dm ⇒ dm.name == data._2.name).get + + val calleesEOptP = ps(dm, Callees.key) + if (calleesEOptP.hasUBP) { + determinePossibleStrings(data, calleesEOptP.ub) + } else { + val dependees = Iterable(calleesEOptP) + InterimResult( + calleesEOptP, + StringConstancyProperty.lowerBound, + StringConstancyProperty.upperBound, + dependees, + calleesContinuation(calleesEOptP, dependees, data) + ) + } + } + + private def determinePossibleStrings( + data: P, callees: Callees + ): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lowerBound.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) @@ -111,7 +136,7 @@ class InterproceduralStringAnalysis( val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ - return processFinalP(data, dependees.values, state, ep.e, p) + return processFinalP(data, callees, dependees.values, state, ep.e, p) case _ ⇒ dependees.put(toAnalyze, ep) } @@ -121,7 +146,7 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val interHandler = InterpretationHandler(cfg) + val interHandler = InterproceduralInterpretationHandler(cfg, callees) sci = StringConstancyInformation.reduceMultiple( uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList ) @@ -133,19 +158,32 @@ class InterproceduralStringAnalysis( StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, dependees.values, - continuation(data, dependees.values, state) + continuation(data, callees, dependees.values, state) ) } else { Result(data, StringConstancyProperty(sci)) } } + private def calleesContinuation( + e: Entity, + dependees: Iterable[EOptionP[DeclaredMethod, Callees]], + inputData: P + )(eps: SomeEPS): ProperPropertyComputationResult = eps match { + case FinalP(callees: Callees) ⇒ + determinePossibleStrings(inputData, callees) + case InterimLUBP(lb, ub) ⇒ + InterimResult(e, lb, ub, dependees, calleesContinuation(e, dependees, inputData)) + case _ ⇒ throw new IllegalStateException("can occur?") + } + /** * `processFinalP` is responsible for handling the case that the `propertyStore` outputs a - * [[FinalP]]. + * [[org.opalj.fpcf.FinalP]]. */ private def processFinalP( data: P, + callees: Callees, dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState, e: Entity, @@ -169,7 +207,7 @@ class InterproceduralStringAnalysis( StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, remDependees, - continuation(data, remDependees, state) + continuation(data, callees, remDependees, state) ) } } @@ -185,20 +223,21 @@ class InterproceduralStringAnalysis( */ private def continuation( data: P, + callees: Callees, dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case FinalP(p) ⇒ processFinalP(data, dependees, state, eps.e, p) + case FinalP(p) ⇒ processFinalP(data, callees, dependees, state, eps.e, p) case InterimLUBP(lb, ub) ⇒ InterimResult( - data, lb, ub, dependees, continuation(data, dependees, state) + data, lb, ub, dependees, continuation(data, callees, dependees, state) ) case _ ⇒ throw new IllegalStateException("Could not process the continuation successfully.") } /** * Helper / accumulator function for finding dependees. For how dependees are detected, see - * [[findDependentVars]]. Returns a list of pairs of DUVar and the index of the - * [[FlatPathElement.element]] in which it occurs. + * findDependentVars. Returns a list of pairs of DUVar and the index of the + * FlatPathElement.element in which it occurs. */ private def findDependeesAcc( subpath: SubPath, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala index 5b2bd41902..3c649db911 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala @@ -29,6 +29,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement @@ -125,7 +126,7 @@ class LocalStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val interHandler = InterpretationHandler(cfg) + val interHandler = IntraproceduralInterpretationHandler(cfg) sci = StringConstancyInformation.reduceMultiple( uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 58540e2528..2a87267eb0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -12,6 +12,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V * @param exprHandler In order to interpret an instruction, it might be necessary to interpret * another instruction in the first place. `exprHandler` makes this possible. * + * @note The abstract type [[InterpretationHandler]] allows the handling of different styles (e.g., + * intraprocedural and interprocedural). Thus, implementation of this class are required to + * clearly indicate what kind of [[InterpretationHandler]] they expect in order to ensure the + * desired behavior and not confuse developers. + * * @author Patrick Mell */ abstract class AbstractStringInterpreter( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala index 33a39ba9ea..3f9be30411 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala @@ -13,6 +13,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `BinaryExprInterpreter` is responsible for processing [[BinaryExpr]]ions. A list of currently * supported binary expressions can be found in the documentation of [[interpret]]. + *

      + * For this interpreter, it is of no relevance what concrete implementation of + * [[InterpretationHandler]] is passed. * * @see [[AbstractStringInterpreter]] * @author Patrick Mell @@ -42,7 +45,6 @@ class BinaryExprInterpreter( List(InterpretationHandler.getConstancyInformationForDynamicInt) case ComputationalTypeFloat ⇒ List(InterpretationHandler.getConstancyInformationForDynamicFloat) - case _ ⇒ List() } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala index 1aea7df7bb..0258137822 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala @@ -12,6 +12,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `IntegerValueInterpreter` is responsible for processing [[IntConst]]s. + *

      + * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. * * @see [[AbstractStringInterpreter]] * diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index ec1651eb42..6b34fa6726 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -5,40 +5,27 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType -import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment -import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr -import org.opalj.tac.ExprStmt -import org.opalj.tac.GetField -import org.opalj.tac.IntConst import org.opalj.tac.New -import org.opalj.tac.NonVirtualFunctionCall -import org.opalj.tac.NonVirtualMethodCall -import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt -import org.opalj.tac.StringConst -import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall -import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.TACStmts -/** - * `InterpretationHandler` is responsible for processing expressions that are relevant in order to - * determine which value(s) a string read operation might have. These expressions usually come from - * the definitions sites of the variable of interest. - * - * @param cfg The control flow graph that underlies the program / method in which the expressions of - * interest reside. - * @author Patrick Mell - */ -class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { - private val stmts = cfg.code.instructions - private val processedDefSites = ListBuffer[Int]() +abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { + + /** + * The statements of the given [[cfg]]. + */ + protected val stmts: Array[Stmt[V]] = cfg.code.instructions + /** + * A list of definition sites that have already been processed. + */ + protected val processedDefSites: ListBuffer[Int] = ListBuffer[Int]() /** * Processes a given definition site. That is, this function determines the interpretation of @@ -52,60 +39,22 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { * case the rules listed above or the ones of the different processors are not met, an * empty list will be returned. */ - def processDefSite(defSite: Int): List[StringConstancyInformation] = { - // Function parameters are not evaluated but regarded as unknown - if (defSite < 0) { - return List(StringConstancyProperty.lowerBound.stringConstancyInformation) - } else if (processedDefSites.contains(defSite)) { - return List() - } - processedDefSites.append(defSite) - - stmts(defSite) match { - case Assignment(_, _, expr: StringConst) ⇒ - new StringConstInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: IntConst) ⇒ - new IntegerValueInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new ArrayInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: New) ⇒ - new NewInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - new VirtualFunctionCallInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ - new StaticFunctionCallInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: BinaryExpr[V]) ⇒ - new BinaryExprInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ - new NonVirtualFunctionCallInterpreter(cfg, this).interpret(expr) - case Assignment(_, _, expr: GetField[V]) ⇒ - new FieldInterpreter(cfg, this).interpret(expr) - case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ - new VirtualFunctionCallInterpreter(cfg, this).interpret(expr) - case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ - new StaticFunctionCallInterpreter(cfg, this).interpret(expr) - case vmc: VirtualMethodCall[V] ⇒ - new VirtualMethodCallInterpreter(cfg, this).interpret(vmc) - case nvmc: NonVirtualMethodCall[V] ⇒ - new NonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) - case _ ⇒ List() - - } - } + def processDefSite(defSite: Int): List[StringConstancyInformation] /** - * This function serves as a wrapper function for [[InterpretationHandler.processDefSite]] in - * the sense that it processes multiple definition sites. Thus, it may throw an exception as - * well if an expression referenced by a definition site cannot be processed. The same rules as - * for [[InterpretationHandler.processDefSite]] apply. + * This function serves as a wrapper function for [[processDefSites]] in the sense that it + * processes multiple definition sites. Thus, it may throw an exception as well if an expression + * referenced by a definition site cannot be processed. The same rules as for [[processDefSite]] + * apply. * * @param defSites The definition sites to process. + * * @return Returns a list of lists of [[StringConstancyInformation]]. Note that this function * preserves the order of the given `defSites`, i.e., the first element in the result * list corresponds to the first element in `defSites` and so on. If a site could not be * processed, the list for that site will be the empty list. */ - def processDefSites(defSites: Array[Int]): List[List[StringConstancyInformation]] = + final def processDefSites(defSites: Array[Int]): List[List[StringConstancyInformation]] = defSites.length match { case 0 ⇒ List() case 1 ⇒ List(processDefSite(defSites.head)) @@ -113,11 +62,12 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { } /** - * The [[InterpretationHandler]] keeps an internal state for correct and faster processing. As + * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As * long as a single object within a CFG is analyzed, there is no need to reset the state. * However, when analyzing a second object (even the same object) it is necessary to call * `reset` to reset the internal state. Otherwise, incorrect results will be produced. - * (Alternatively, you could instantiate another [[InterpretationHandler]] instance.) + * (Alternatively, another instance of an implementation of [[InterpretationHandler]] could be + * instantiated.) */ def reset(): Unit = { processedDefSites.clear() @@ -127,12 +77,6 @@ class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { object InterpretationHandler { - /** - * @see [[InterpretationHandler]] - */ - def apply(cfg: CFG[Stmt[V], TACStmts[V]]): InterpretationHandler = - new InterpretationHandler(cfg) - /** * Checks whether an expression contains a call to [[StringBuilder#toString]] or * [[StringBuffer#toString]]. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala new file mode 100644 index 0000000000..83aee36017 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala @@ -0,0 +1,76 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import scala.collection.mutable.ListBuffer + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.ArrayLoad +import org.opalj.tac.ArrayStore +import org.opalj.tac.Assignment +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as + * [[ArrayStore]] expressions in an interprocedural fashion. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralArrayInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = ArrayLoad[V] + + /** + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + // TODO: Change from intra- to interprocedural + val stmts = cfg.code.instructions + val children = ListBuffer[StringConstancyInformation]() + // Loop over all possible array values + val defSites = instr.arrayRef.asVar.definedBy.toArray + defSites.filter(_ >= 0).sorted.foreach { next ⇒ + val arrDecl = stmts(next) + val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + // Process ArrayStores + sortedArrDeclUses.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted + sortedDefs.map { exprHandler.processDefSite }.foreach { + children.appendAll(_) + } + } + // Process ArrayLoads + sortedArrDeclUses.filter { + stmts(_) match { + case Assignment(_, _, _: ArrayLoad[V]) ⇒ true + case _ ⇒ false + } + } foreach { f: Int ⇒ + val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy + defs.toArray.sorted.map { exprHandler.processDefSite }.foreach { + children.appendAll(_) + } + } + } + + // In case it refers to a method parameter, add a dynamic string property + if (defSites.exists(_ < 0)) { + children.append(StringConstancyProperty.lowerBound.stringConstancyInformation) + } + + children.toList + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala new file mode 100644 index 0000000000..8d6daa2579 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala @@ -0,0 +1,46 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.GetField +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this + * implementation, there is currently only primitive support for fields, i.e., they are not analyzed + * but a constant [[StringConstancyInformation]] is returned (see [[interpret]] of this class). + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralFieldInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = GetField[V] + + /** + * Currently, fields are not interpreted. Thus, this function always returns a list with a + * single element consisting of [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + // TODO: Change from intra- to interprocedural + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + )) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala new file mode 100644 index 0000000000..8a5f8821aa --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -0,0 +1,103 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.ArrayLoad +import org.opalj.tac.Assignment +import org.opalj.tac.BinaryExpr +import org.opalj.tac.ExprStmt +import org.opalj.tac.GetField +import org.opalj.tac.IntConst +import org.opalj.tac.New +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.StringConst +import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.VirtualMethodCall + +/** + * `InterproceduralInterpretationHandler` is responsible for processing expressions that are + * relevant in order to determine which value(s) a string read operation might have. These + * expressions usually come from the definitions sites of the variable of interest. + * + * @param cfg The control flow graph that underlies the program / method in which the expressions of + * interest reside. + * @author Patrick Mell + */ +class InterproceduralInterpretationHandler( + cfg: CFG[Stmt[V], TACStmts[V]], + callees: Callees +) extends InterpretationHandler(cfg) { + + /** + * Processed the given definition site in an interprocedural fashion. + *

      + * @inheritdoc + */ + override def processDefSite(defSite: Int): List[StringConstancyInformation] = { + // Function parameters are not evaluated but regarded as unknown + if (defSite < 0) { + return List(StringConstancyProperty.lowerBound.stringConstancyInformation) + } else if (processedDefSites.contains(defSite)) { + return List() + } + processedDefSites.append(defSite) + + stmts(defSite) match { + case Assignment(_, _, expr: StringConst) ⇒ + new StringConstInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: IntConst) ⇒ + new IntegerValueInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: ArrayLoad[V]) ⇒ + new InterproceduralArrayInterpreter(cfg, this, callees).interpret(expr) + case Assignment(_, _, expr: New) ⇒ + new NewInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + new InterproceduralVirtualFunctionCallInterpreter( + cfg, this, callees + ).interpret(expr) + case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ + new InterproceduralStaticFunctionCallInterpreter(cfg, this, callees).interpret(expr) + case Assignment(_, _, expr: BinaryExpr[V]) ⇒ + new BinaryExprInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ + new InterproceduralNonVirtualFunctionCallInterpreter( + cfg, this, callees + ).interpret(expr) + case Assignment(_, _, expr: GetField[V]) ⇒ + new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr) + case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ + new InterproceduralVirtualFunctionCallInterpreter( + cfg, this, callees + ).interpret(expr) + case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ + new InterproceduralStaticFunctionCallInterpreter(cfg, this, callees).interpret(expr) + case vmc: VirtualMethodCall[V] ⇒ + new InterproceduralVirtualMethodCallInterpreter(cfg, this, callees).interpret(vmc) + case nvmc: NonVirtualMethodCall[V] ⇒ + new InterproceduralNonVirtualMethodCallInterpreter( + cfg, this, callees + ).interpret(nvmc) + case _ ⇒ List() + + } + } + +} + +object InterproceduralInterpretationHandler { + + /** + * @see [[IntraproceduralInterpretationHandler]] + */ + def apply( + cfg: CFG[Stmt[V], TACStmts[V]], callees: Callees + ): IntraproceduralInterpretationHandler = new IntraproceduralInterpretationHandler(cfg) + +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala new file mode 100644 index 0000000000..fe5fba5860 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralNonVirtualFunctionCallInterpreter` is responsible for processing + * [[NonVirtualFunctionCall]]s in an interprocedural fashion. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralNonVirtualFunctionCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = NonVirtualFunctionCall[V] + + /** + * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns + * a list with a single element consisting of [[StringConstancyLevel.DYNAMIC]], + * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + // TODO: Change from intra- to interprocedural + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + )) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala new file mode 100644 index 0000000000..38cb236fe4 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -0,0 +1,71 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import scala.collection.mutable.ListBuffer + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralNonVirtualMethodCallInterpreter` is responsible for processing + * [[NonVirtualMethodCall]]s in an interprocedural fashion. + * For supported method calls, see the documentation of the `interpret` function. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralNonVirtualMethodCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = NonVirtualMethodCall[V] + + /** + * Currently, this function supports the interpretation of the following non virtual methods: + *

        + *
      • + * `<init>`, when initializing an object (for this case, currently zero constructor or + * one constructor parameter are supported; if more params are available, only the very first + * one is interpreted). + *
      • + *
      + * For all other calls, an empty list will be returned at the moment. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { + // TODO: Change from intra- to interprocedural + instr.name match { + case "" ⇒ interpretInit(instr) + case _ ⇒ List() + } + } + + /** + * Processes an `<init>` method call. If it has no parameters, an empty list will be + * returned. Otherwise, only the very first parameter will be evaluated and its result returned + * (this is reasonable as both, [[StringBuffer]] and [[StringBuilder]], have only constructors + * with <= 0 arguments and only these are currently interpreted). + */ + private def interpretInit(init: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { + init.params.size match { + case 0 ⇒ List() + //List(StringConstancyInformation(StringConstancyLevel.CONSTANT, "")) + case _ ⇒ + val scis = ListBuffer[StringConstancyInformation]() + init.params.head.asVar.definedBy.foreach { ds ⇒ + scis.append(exprHandler.processDefSite(ds): _*) + } + scis.toList + } + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala new file mode 100644 index 0000000000..cbf6a9046b --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -0,0 +1,47 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing + * [[StaticFunctionCall]]s in an interprocedural fashion. + *

      + * For supported method calls, see the documentation of the `interpret` function. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralStaticFunctionCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = StaticFunctionCall[V] + + /** + * This function always returns a list with a single element consisting of + * [[StringConstancyLevel.DYNAMIC]], [[StringConstancyType.APPEND]], and + * [[StringConstancyInformation.UnknownWordSymbol]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = + // TODO: Change from intra- to interprocedural + List(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + )) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala new file mode 100644 index 0000000000..850914b71a --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala @@ -0,0 +1,195 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.ComputationalTypeFloat +import org.opalj.br.ComputationalTypeInt +import org.opalj.br.ObjectType +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing + * [[VirtualFunctionCall]]s in an interprocedural fashion. + * The list of currently supported function calls can be seen in the documentation of [[interpret]]. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralVirtualFunctionCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = VirtualFunctionCall[V] + + /** + * Currently, this implementation supports the interpretation of the following function calls: + *

        + *
      • `append`: Calls to the `append` function of [[StringBuilder]] and [[StringBuffer]].
      • + *
      • + * `toString`: Calls to the `append` function of [[StringBuilder]] and [[StringBuffer]]. As + * a `toString` call does not change the state of such an object, an empty list will be + * returned. + *
      • + *
      • + * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For + * further information how this operation is processed, see + * [[InterproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. + *
      • + *
      • + * Apart from these supported methods, a list with [[StringConstancyProperty.lowerBound]] + * will be returned in case the passed method returns a [[java.lang.String]]. + *
      • + *
      + * + * If none of the above-described cases match, an empty list will be returned. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + // TODO: Change from intra- to interprocedural + instr.name match { + case "append" ⇒ interpretAppendCall(instr).getOrElse(List()) + case "toString" ⇒ interpretToStringCall(instr) + case "replace" ⇒ interpretReplaceCall(instr) + case _ ⇒ + instr.descriptor.returnType match { + case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ + List(StringConstancyProperty.lowerBound.stringConstancyInformation) + case _ ⇒ List() + } + } + } + + /** + * Function for processing calls to [[StringBuilder#append]] or [[StringBuffer#append]]. Note + * that this function assumes that the given `appendCall` is such a function call! Otherwise, + * the expected behavior cannot be guaranteed. + */ + private def interpretAppendCall( + appendCall: VirtualFunctionCall[V] + ): Option[List[StringConstancyInformation]] = { + val receiverValues = receiverValuesOfAppendCall(appendCall) + val appendValue = valueOfAppendCall(appendCall) + + // The case can occur that receiver and append value are empty; although, it is + // counter-intuitive, this case may occur if both, the receiver and the parameter, have been + // processed before + if (receiverValues.isEmpty && appendValue.isEmpty) { + None + } // It might be that we have to go back as much as to a New expression. As they do not + // produce a result (= empty list), the if part + else if (receiverValues.isEmpty) { + Some(List(appendValue.get)) + } // The append value might be empty, if the site has already been processed (then this + // information will come from another StringConstancyInformation object + else if (appendValue.isEmpty) { + Some(receiverValues) + } // Receiver and parameter information are available => Combine them + else { + Some(receiverValues.map { nextSci ⇒ + StringConstancyInformation( + StringConstancyLevel.determineForConcat( + nextSci.constancyLevel, appendValue.get.constancyLevel + ), + StringConstancyType.APPEND, + nextSci.possibleStrings + appendValue.get.possibleStrings + ) + }) + } + } + + /** + * This function determines the current value of the receiver object of an `append` call. + */ + private def receiverValuesOfAppendCall( + call: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = + // There might be several receivers, thus the map; from the processed sites, however, use + // only the head as a single receiver interpretation will produce one element + call.receiver.asVar.definedBy.toArray.sorted.map( + exprHandler.processDefSite + ).filter(_.nonEmpty).map(_.head).toList + + /** + * Determines the (string) value that was passed to a `String{Builder, Buffer}#append` method. + * This function can process string constants as well as function calls as argument to append. + */ + private def valueOfAppendCall( + call: VirtualFunctionCall[V] + ): Option[StringConstancyInformation] = { + val param = call.params.head.asVar + // .head because we want to evaluate only the first argument of append + val defSiteParamHead = param.definedBy.head + var value = exprHandler.processDefSite(defSiteParamHead) + // If defSiteParamHead points to a New, value will be the empty list. In that case, process + // the first use site (which is the call) + if (value.isEmpty) { + value = exprHandler.processDefSite( + cfg.code.instructions(defSiteParamHead).asAssignment.targetVar.usedBy.toArray.min + ) + } + param.value.computationalType match { + // For some types, we know the (dynamic) values + case ComputationalTypeInt ⇒ + // The value was already computed above; however, we need to check whether the + // append takes an int value or a char (if it is a constant char, convert it) + if (call.descriptor.parameterType(0).isCharType && + value.head.constancyLevel == StringConstancyLevel.CONSTANT) { + Some(value.head.copy( + possibleStrings = value.head.possibleStrings.toInt.toChar.toString + )) + } else { + Some(value.head) + } + case ComputationalTypeFloat ⇒ + Some(InterpretationHandler.getConstancyInformationForDynamicFloat) + // Otherwise, try to compute + case _ ⇒ + // It might be necessary to merge the values of the receiver and of the parameter + value.size match { + case 0 ⇒ None + case 1 ⇒ Some(value.head) + case _ ⇒ Some(StringConstancyInformation( + StringConstancyLevel.determineForConcat( + value.head.constancyLevel, value(1).constancyLevel + ), + StringConstancyType.APPEND, + value.head.possibleStrings + value(1).possibleStrings + )) + } + } + } + + /** + * Function for processing calls to [[StringBuilder#toString]] or [[StringBuffer#toString]]. + * Note that this function assumes that the given `toString` is such a function call! Otherwise, + * the expected behavior cannot be guaranteed. + */ + private def interpretToStringCall( + call: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = + exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + + /** + * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. + * Currently, this function simply approximates `replace` functions by returning a list with one + * element - the element currently is provided by + * [[InterpretationHandler.getStringConstancyInformationForReplace]]. + */ + private def interpretReplaceCall( + instr: VirtualFunctionCall[V] + ): List[StringConstancyInformation] = + List(InterpretationHandler.getStringConstancyInformationForReplace) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala new file mode 100644 index 0000000000..745410946a --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala @@ -0,0 +1,55 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralVirtualMethodCallInterpreter` is responsible for processing + * [[VirtualMethodCall]]s in an interprocedural fashion. + * For supported method calls, see the documentation of the `interpret` function. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralVirtualMethodCallInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + callees: Callees +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = VirtualMethodCall[V] + + /** + * Currently, this function supports the interpretation of the following virtual methods: + *
        + *
      • + * `setLength`: `setLength` is a method to reset / clear a [[StringBuilder]] / [[StringBuffer]] + * (at least when called with the argument `0`). For simplicity, this interpreter currently + * assumes that 0 is always passed, i.e., the `setLength` method is currently always regarded as + * a reset mechanism. + *
      • + *
      + * For all other calls, an empty list will be returned. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): List[StringConstancyInformation] = { + // TODO: Change from intra- to interprocedural + instr.name match { + case "setLength" ⇒ List(StringConstancyInformation( + StringConstancyLevel.CONSTANT, StringConstancyType.RESET + )) + case _ ⇒ List() + } + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala similarity index 90% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala index ca1cf911b7..107dfa8722 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala @@ -14,16 +14,16 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `ArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as [[ArrayStore]] - * expressions. + * The `IntraproceduralArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as + * [[ArrayStore]] expressions in an intraprocedural fashion. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class ArrayInterpreter( +class IntraproceduralArrayInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = ArrayLoad[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala similarity index 77% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FieldInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala index 00e1d2f0cf..e9b2bbe162 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala @@ -11,17 +11,17 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `FieldInterpreter` is responsible for processing [[GetField]]s. Currently, there is only - * primitive support for fields, i.e., they are not analyzed but a constant - * [[StringConstancyInformation]] is returned. + * The `IntraproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this + * implementation, there is currently only primitive support for fields, i.e., they are not analyzed + * but a constant [[StringConstancyInformation]] is returned (see [[interpret]] of this class). * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class FieldInterpreter( +class IntraproceduralFieldInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = GetField[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/GetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala similarity index 79% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/GetStaticInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala index a0fcea91c6..ecb6ef5624 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/GetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala @@ -11,17 +11,17 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `GetStaticInterpreter` is responsible for processing [[org.opalj.tac.GetStatic]]s. Currently, - * there is only primitive support, i.e., they are not analyzed but a fixed - * [[StringConstancyInformation]] is returned. + * The `IntraproceduralGetStaticInterpreter` is responsible for processing + * [[org.opalj.tac.GetStatic]]s in an intraprocedural fashion. Thus, they are not analyzed but a + * fixed [[StringConstancyInformation]] is returned (see [[interpret]]). * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class GetStaticInterpreter( +class IntraproceduralGetStaticInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = GetStatic diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala new file mode 100644 index 0000000000..68db868e76 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -0,0 +1,94 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.ArrayLoad +import org.opalj.tac.Assignment +import org.opalj.tac.BinaryExpr +import org.opalj.tac.ExprStmt +import org.opalj.tac.GetField +import org.opalj.tac.IntConst +import org.opalj.tac.New +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.Stmt +import org.opalj.tac.StringConst +import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are + * relevant in order to determine which value(s) a string read operation might have. These + * expressions usually come from the definitions sites of the variable of interest. + * + * @param cfg The control flow graph that underlies the program / method in which the expressions of + * interest reside. + * @author Patrick Mell + */ +class IntraproceduralInterpretationHandler( + cfg: CFG[Stmt[V], TACStmts[V]] +) extends InterpretationHandler(cfg) { + + /** + * Processed the given definition site in an intraprocedural fashion. + *

      + * @inheritdoc + */ + override def processDefSite(defSite: Int): List[StringConstancyInformation] = { + // Function parameters are not evaluated but regarded as unknown + if (defSite < 0) { + return List(StringConstancyProperty.lowerBound.stringConstancyInformation) + } else if (processedDefSites.contains(defSite)) { + return List() + } + processedDefSites.append(defSite) + + stmts(defSite) match { + case Assignment(_, _, expr: StringConst) ⇒ + new StringConstInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: IntConst) ⇒ + new IntegerValueInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: ArrayLoad[V]) ⇒ + new IntraproceduralArrayInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: New) ⇒ + new NewInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ + new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ + new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: BinaryExpr[V]) ⇒ + new BinaryExprInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ + new IntraproceduralNonVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case Assignment(_, _, expr: GetField[V]) ⇒ + new IntraproceduralFieldInterpreter(cfg, this).interpret(expr) + case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ + new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ + new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr) + case vmc: VirtualMethodCall[V] ⇒ + new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc) + case nvmc: NonVirtualMethodCall[V] ⇒ + new IntraproceduralNonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) + case _ ⇒ List() + + } + } + +} + +object IntraproceduralInterpretationHandler { + + /** + * @see [[IntraproceduralInterpretationHandler]] + */ + def apply( + cfg: CFG[Stmt[V], TACStmts[V]] + ): IntraproceduralInterpretationHandler = new IntraproceduralInterpretationHandler(cfg) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala similarity index 83% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala index 2a5bfe997e..950a4d44eb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala @@ -11,16 +11,16 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `NonVirtualFunctionCallInterpreter` is responsible for processing - * [[NonVirtualFunctionCall]]s. + * The `IntraproceduralNonVirtualFunctionCallInterpreter` is responsible for processing + * [[NonVirtualFunctionCall]]s in an intraprocedural fashion. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class NonVirtualFunctionCallInterpreter( +class IntraproceduralNonVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler, ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala similarity index 89% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala index eae77e81c4..916feebc81 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala @@ -11,16 +11,17 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `NonVirtualMethodCallInterpreter` is responsible for processing [[NonVirtualMethodCall]]s. + * The `IntraproceduralNonVirtualMethodCallInterpreter` is responsible for processing + * [[NonVirtualMethodCall]]s in an intraprocedural fashion. * For supported method calls, see the documentation of the `interpret` function. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class NonVirtualMethodCallInterpreter( +class IntraproceduralNonVirtualMethodCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] @@ -53,8 +54,7 @@ class NonVirtualMethodCallInterpreter( */ private def interpretInit(init: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { init.params.size match { - case 0 ⇒ - List() + case 0 ⇒ List() //List(StringConstancyInformation(StringConstancyLevel.CONSTANT, "")) case _ ⇒ val scis = ListBuffer[StringConstancyInformation]() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala similarity index 71% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala index 4dc69b6dcd..83c6fefa93 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala @@ -11,24 +11,26 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `StaticFunctionCallInterpreter` is responsible for processing [[StaticFunctionCall]]s. + * The `IntraproceduralStaticFunctionCallInterpreter` is responsible for processing + * [[StaticFunctionCall]]s in an intraprocedural fashion. + *

      * For supported method calls, see the documentation of the `interpret` function. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class StaticFunctionCallInterpreter( +class IntraproceduralStaticFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] /** - * Currently, [[StaticFunctionCall]]s are not supported. Thus, this function always returns a - * list with a single element consisting of [[StringConstancyLevel.DYNAMIC]], - * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * This function always returns a list with a single element consisting of + * [[StringConstancyLevel.DYNAMIC]], [[StringConstancyType.APPEND]], and + * [[StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala index 121865b94a..0b8c4f7134 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -15,17 +15,17 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `VirtualFunctionCallInterpreter` is responsible for processing [[VirtualFunctionCall]]s. - * The list of currently supported function calls can be seen in the documentation of - * [[interpret]]. + * The `IntraproceduralVirtualFunctionCallInterpreter` is responsible for processing + * [[VirtualFunctionCall]]s in an intraprocedural fashion. + * The list of currently supported function calls can be seen in the documentation of [[interpret]]. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class VirtualFunctionCallInterpreter( +class IntraproceduralVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -42,7 +42,7 @@ class VirtualFunctionCallInterpreter( *

    • * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For * further information how this operation is processed, see - * [[VirtualFunctionCallInterpreter.interpretReplaceCall]]. + * [[IntraproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. *
    • *
    • * Apart from these supported methods, a list with [[StringConstancyProperty.lowerBound]] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala similarity index 87% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala index fe072f3b01..53e0be513b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/VirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala @@ -11,16 +11,17 @@ import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V /** - * The `VirtualMethodCallInterpreter` is responsible for processing [[VirtualMethodCall]]s. + * The `IntraproceduralVirtualMethodCallInterpreter` is responsible for processing + * [[VirtualMethodCall]]s in an intraprocedural fashion. * For supported method calls, see the documentation of the `interpret` function. * * @see [[AbstractStringInterpreter]] * * @author Patrick Mell */ -class VirtualMethodCallInterpreter( +class IntraproceduralVirtualMethodCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterpretationHandler + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala index c095eb294e..b0652e77b9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala @@ -10,6 +10,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `NewInterpreter` is responsible for processing [[New]] expressions. + *

      + * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. * * @see [[AbstractStringInterpreter]] * diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala index 751da65a65..2a5289887e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala @@ -12,6 +12,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `StringConstInterpreter` is responsible for processing [[StringConst]]s. + *

      + * For this interpreter, the given interpretation handler does not play any role. Consequently, any + * implementation may be passed. * * @see [[AbstractStringInterpreter]] * diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 34699e1812..f371699cc8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -14,7 +14,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringTreeRepetition import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler /** * [[PathTransformer]] is responsible for transforming a [[Path]] into another representation, such @@ -29,7 +29,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation */ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { - private val exprHandler = InterpretationHandler(cfg) + private val exprHandler = IntraproceduralInterpretationHandler(cfg) /** * Accumulator function for transforming a path into a StringTree element. @@ -108,18 +108,19 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * Takes a [[Path]] and transforms it into a [[StringTree]]. This implies an interpretation of * how to handle methods called on the object of interest (like `append`). * - * @param path The path element to be transformed. - * @param fpe2Sci A mapping from [[FlatPathElement.element]] values to - * [[StringConstancyInformation]]. Make use of this mapping if some - * StringConstancyInformation need to be used that the [[InterpretationHandler]] - * cannot infer / derive. For instance, if the exact value of an expression needs - * to be determined by calling the - * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis]] - * on another instance, store this information in fpe2Sci. - * @param resetExprHandler Whether to reset the underlying [[InterpretationHandler]] or not. + * @param path The path element to be transformed. + * @param fpe2Sci A mapping from [[FlatPathElement.element]] values to + * [[StringConstancyInformation]]. Make use of this mapping if some + * StringConstancyInformation need to be used that the [[IntraproceduralInterpretationHandler]] + * cannot infer / derive. For instance, if the exact value of an expression needs + * to be determined by calling the + * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis]] + * on another instance, store this information in fpe2Sci. + * @param resetExprHandler Whether to reset the underlying [[IntraproceduralInterpretationHandler]] or not. * When calling this function from outside, the default value should do * fine in most of the cases. For further information, see - * [[InterpretationHandler.reset]]. + * [[IntraproceduralInterpretationHandler.reset]]. + * * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed * [[org.opalj.br.fpcf.properties.properties.StringTree]] will be returned. Note that all elements of the tree will be defined, * i.e., if `path` contains sites that could not be processed (successfully), they will From 946d3774ce93a516959cf949fb3a0b1ba4f7f44f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 28 Jan 2019 20:19:32 +0100 Subject: [PATCH 139/316] Fixed a copy & paste error in the "apply" method. --- .../interpretation/InterproceduralInterpretationHandler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 8a5f8821aa..a635a4a6f9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -98,6 +98,6 @@ object InterproceduralInterpretationHandler { */ def apply( cfg: CFG[Stmt[V], TACStmts[V]], callees: Callees - ): IntraproceduralInterpretationHandler = new IntraproceduralInterpretationHandler(cfg) + ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler(cfg, callees) } \ No newline at end of file From b2b87583cff7ee597dca2d25ce0d068977842416 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 29 Jan 2019 11:37:02 +0100 Subject: [PATCH 140/316] Extended the constructor to take further information required for interprocedural processing. --- .../InterproceduralStringAnalysis.scala | 9 +++++++-- .../InterproceduralInterpretationHandler.scala | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 5a59015d88..6c1330e3d0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -25,6 +25,7 @@ import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.DeclaredMethod +import org.opalj.br.analyses.DeclaredMethods import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty @@ -54,6 +55,8 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { + private var declaredMethods: DeclaredMethods = _ + /** * This class is to be used to store state information that are required at a later point in * time during the analysis, e.g., due to the fact that another analysis had to be triggered to @@ -71,7 +74,7 @@ class InterproceduralStringAnalysis( ) def analyze(data: P): ProperPropertyComputationResult = { - val declaredMethods = project.get(DeclaredMethodsKey) + declaredMethods = project.get(DeclaredMethodsKey) // TODO: Is there a way to get the declared method in constant time? val dm = declaredMethods.declaredMethods.find(dm ⇒ dm.name == data._2.name).get @@ -146,7 +149,9 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val interHandler = InterproceduralInterpretationHandler(cfg, callees) + val interHandler = InterproceduralInterpretationHandler( + cfg, ps, declaredMethods, callees + ) sci = StringConstancyInformation.reduceMultiple( uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index a635a4a6f9..16f83a540b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -1,5 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.PropertyStore +import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -31,8 +33,10 @@ import org.opalj.tac.VirtualMethodCall * @author Patrick Mell */ class InterproceduralInterpretationHandler( - cfg: CFG[Stmt[V], TACStmts[V]], - callees: Callees + cfg: CFG[Stmt[V], TACStmts[V]], + ps: PropertyStore, + declaredMethods: DeclaredMethods, + callees: Callees ) extends InterpretationHandler(cfg) { /** @@ -97,7 +101,12 @@ object InterproceduralInterpretationHandler { * @see [[IntraproceduralInterpretationHandler]] */ def apply( - cfg: CFG[Stmt[V], TACStmts[V]], callees: Callees - ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler(cfg, callees) + cfg: CFG[Stmt[V], TACStmts[V]], + ps: PropertyStore, + declaredMethods: DeclaredMethods, + callees: Callees + ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( + cfg, ps, declaredMethods, callees + ) } \ No newline at end of file From d060592d2729ef0404dfbb0c2288405f25ea84f9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 29 Jan 2019 20:27:46 +0100 Subject: [PATCH 141/316] Changed the interface of the InterpretationHandler and AbstractStringInterpreter (to now return a "Result" object). As a result, many files had to be touched. --- .../string_analysis/LocalTestMethods.java | 2 +- .../properties/StringConstancyProperty.scala | 15 ++ .../StringConstancyInformation.scala | 49 ++++-- .../InterproceduralStringAnalysis.scala | 11 +- .../string_analysis/LocalStringAnalysis.scala | 5 +- .../AbstractStringInterpreter.scala | 17 ++- .../BinaryExprInterpreter.scala | 21 +-- .../IntegerValueInterpreter.scala | 9 +- .../InterpretationHandler.scala | 46 ++---- .../InterproceduralArrayInterpreter.scala | 20 ++- .../InterproceduralFieldInterpreter.scala | 24 ++- ...InterproceduralInterpretationHandler.scala | 24 ++- ...ralNonVirtualFunctionCallInterpreter.scala | 21 ++- ...duralNonVirtualMethodCallInterpreter.scala | 33 +++-- ...ceduralStaticFunctionCallInterpreter.scala | 20 +-- ...eduralVirtualFunctionCallInterpreter.scala | 131 +++++++++-------- ...oceduralVirtualMethodCallInterpreter.scala | 15 +- .../IntraproceduralArrayInterpreter.scala | 24 ++- .../IntraproceduralFieldInterpreter.scala | 22 ++- .../IntraproceduralGetStaticInterpreter.scala | 22 ++- ...IntraproceduralInterpretationHandler.scala | 22 ++- ...ralNonVirtualFunctionCallInterpreter.scala | 20 +-- ...duralNonVirtualMethodCallInterpreter.scala | 36 +++-- ...ceduralStaticFunctionCallInterpreter.scala | 18 +-- ...eduralVirtualFunctionCallInterpreter.scala | 139 ++++++++++-------- ...oceduralVirtualMethodCallInterpreter.scala | 18 ++- .../interpretation/NewInterpreter.scala | 11 +- .../StringConstInterpreter.scala | 9 +- .../preprocessing/PathTransformer.scala | 35 +++-- 29 files changed, 473 insertions(+), 366 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index 48c76875b7..a48585dfad 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -189,7 +189,7 @@ public void multipleConstantDefSites(boolean cond) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|\\w|java.lang.System|" + expectedStrings = "((java.lang.Object|\\w)|java.lang.System|" + "java.lang.\\w|\\w)" ) }) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index 54326300cd..a6b5b0fe27 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -26,6 +26,14 @@ class StringConstancyProperty( s"Level: $level, Possible Strings: ${stringConstancyInformation.possibleStrings}" } + /** + * @return Returns `true` if the [[stringConstancyInformation]] contained in this instance is + * the neutral element (see [[StringConstancyInformation.isTheNeutralElement]]). + */ + def isTheNeutralElement: Boolean = { + stringConstancyInformation.isTheNeutralElement + } + } object StringConstancyProperty extends Property with StringConstancyPropertyMetaInformation { @@ -46,6 +54,13 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta stringConstancyInformation: StringConstancyInformation ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) + /** + * @return Returns the / a neutral [[StringConstancyProperty]] element, i.e., an element for + * which [[StringConstancyProperty.isTheNeutralElement]] is `true`. + */ + def getNeutralElement: StringConstancyProperty = + StringConstancyProperty(StringConstancyInformation.getNeutralElement) + /** * @return Returns the upper bound from a lattice-point of view. */ diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 5139e69bce..159b60322f 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -1,8 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.br.fpcf.properties.string_definition -import org.opalj.br.fpcf.properties.StringConstancyProperty - /** * @param possibleStrings Only relevant for some [[StringConstancyType]]s, i.e., sometimes this * parameter can be omitted. @@ -13,7 +11,21 @@ case class StringConstancyInformation( constancyLevel: StringConstancyLevel.Value = StringConstancyLevel.DYNAMIC, constancyType: StringConstancyType.Value = StringConstancyType.APPEND, possibleStrings: String = "" -) +) { + + /** + * Checks whether the instance is the neutral element. + * + * @return Returns `true` iff [[constancyLevel]] equals [[StringConstancyLevel.CONSTANT]], + * [[constancyType]] equals [[StringConstancyType.APPEND]], and + * [[possibleStrings]] equals the empty string. + */ + def isTheNeutralElement: Boolean = + constancyLevel == StringConstancyLevel.CONSTANT && + constancyType == StringConstancyType.APPEND && + possibleStrings == "" + +} /** * Provides a collection of instance-independent but string-constancy related values. @@ -53,23 +65,34 @@ object StringConstancyInformation { * @return Returns the reduced information in the fashion described above. */ def reduceMultiple(scis: List[StringConstancyInformation]): StringConstancyInformation = { - scis.length match { + val relScis = scis.filter(!_.isTheNeutralElement) + relScis.length match { // The list may be empty, e.g., if the UVar passed to the analysis, refers to a // VirtualFunctionCall (they are not interpreted => an empty list is returned) => return - // the corresponding information - case 0 ⇒ StringConstancyProperty.lowerBound.stringConstancyInformation - case 1 ⇒ scis.head + // the neutral element + case 0 ⇒ StringConstancyInformation.getNeutralElement + case 1 ⇒ relScis.head case _ ⇒ // Reduce - val reduced = scis.reduceLeft((o, n) ⇒ StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - StringConstancyType.APPEND, - s"${o.possibleStrings}|${n.possibleStrings}" - )) - // Modify possibleStrings value + val reduced = relScis.reduceLeft((o, n) ⇒ + StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral( + o.constancyLevel, n.constancyLevel + ), + StringConstancyType.APPEND, + s"${o.possibleStrings}|${n.possibleStrings}" + )) + // Add parentheses to possibleStrings value (to indicate a choice) StringConstancyInformation( reduced.constancyLevel, reduced.constancyType, s"(${reduced.possibleStrings})" ) } } + /** + * @return Returns the / a neutral [[StringConstancyInformation]] element, i.e., an element for + * which [[StringConstancyInformation.isTheNeutralElement]] is `true`. + */ + def getNeutralElement: StringConstancyInformation = + StringConstancyInformation(StringConstancyLevel.CONSTANT) + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 6c1330e3d0..1b5a42fe15 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -38,7 +38,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathEleme import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.ExprStmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath @@ -149,11 +149,12 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val interHandler = InterproceduralInterpretationHandler( - cfg, ps, declaredMethods, callees - ) + val interHandler = IntraproceduralInterpretationHandler(cfg) sci = StringConstancyInformation.reduceMultiple( - uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList + uvar.definedBy.toArray.sorted.map { ds ⇒ + val nextResult = interHandler.processDefSite(ds) + nextResult.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }.toList ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala index 3c649db911..a40a736455 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala @@ -128,7 +128,10 @@ class LocalStringAnalysis( else { val interHandler = IntraproceduralInterpretationHandler(cfg) sci = StringConstancyInformation.reduceMultiple( - uvar.definedBy.toArray.sorted.flatMap { interHandler.processDefSite }.toList + uvar.definedBy.toArray.sorted.map { ds ⇒ + val r = interHandler.processDefSite(ds).asInstanceOf[Result] + r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }.toList ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 2a87267eb0..4c5be67824 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -1,8 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -31,12 +31,15 @@ abstract class AbstractStringInterpreter( * @param instr The instruction that is to be interpreted. It is the responsibility of * implementations to make sure that an instruction is properly and comprehensively * evaluated. - * @return The interpreted instruction. An empty list indicates that an instruction was not / - * could not be interpreted (e.g., because it is not supported or it was processed - * before). A list with more than one element indicates an option (only one out of the - * values is possible during runtime of the program); thus, some concatenations must - * already happen within the interpretation. + * @return The interpreted instruction. A neutral StringConstancyProperty contained in the + * result indicates that an instruction was not / could not be interpreted (e.g., + * because it is not supported or it was processed before). + *

      + * As demanded by [[InterpretationHandler]], the entity of the result should be the + * definition site. However, as interpreters know the instruction to interpret but not + * the definition site, this function returns the interpreted instruction as entity. + * Thus, the entity needs to be replaced by the calling client. */ - def interpret(instr: T): List[StringConstancyInformation] + def interpret(instr: T): ProperPropertyComputationResult } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala index 3f9be30411..28927d07ee 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala @@ -1,10 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.BinaryExpr import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -34,18 +37,18 @@ class BinaryExprInterpreter( *

    • [[ComputationalTypeInt]] *
    • [[ComputationalTypeFloat]]
    • * - * To be more precise, that means that a list with one element will be returned. In all other - * cases, an empty list will be returned. + * For all other expressions, a result containing [[StringConstancyProperty.getNeutralElement]] + * will be returned. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - instr.cTpe match { - case ComputationalTypeInt ⇒ - List(InterpretationHandler.getConstancyInformationForDynamicInt) - case ComputationalTypeFloat ⇒ - List(InterpretationHandler.getConstancyInformationForDynamicFloat) - case _ ⇒ List() + override def interpret(instr: T): ProperPropertyComputationResult = { + val sci = instr.cTpe match { + case ComputationalTypeInt ⇒ InterpretationHandler.getConstancyInfoForDynamicInt + case ComputationalTypeFloat ⇒ InterpretationHandler.getConstancyInfoForDynamicFloat + case _ ⇒ StringConstancyInformation.getNeutralElement } + Result(instr, StringConstancyProperty(sci)) + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala index 0258137822..350ee89c88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala @@ -1,10 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.IntConst import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -29,11 +32,11 @@ class IntegerValueInterpreter( /** * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation( + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString - )) + ))) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 6b34fa6726..eda3ecb2d7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -4,10 +4,12 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Assignment import org.opalj.tac.Expr import org.opalj.tac.New @@ -35,31 +37,15 @@ abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { * actually exists, and (3) can be processed by one of the subclasses of * [[AbstractStringInterpreter]] (in case (3) is violated, an * [[IllegalArgumentException]] will be thrown. - * @return Returns a list of interpretations in the form of [[StringConstancyInformation]]. In - * case the rules listed above or the ones of the different processors are not met, an - * empty list will be returned. + * @return Returns the result of the interpretation. Note that depending on the concrete + * interpreter either a final or an intermediate result can be returned! + * In case the rules listed above or the ones of the different concrete interpreters are + * not met, the neutral [[org.opalj.br.fpcf.properties.StringConstancyProperty]] element + * will be encapsulated in the result (see + * [[org.opalj.br.fpcf.properties.StringConstancyProperty.isTheNeutralElement]]). + * The entity of the result will be the given `defSite`. */ - def processDefSite(defSite: Int): List[StringConstancyInformation] - - /** - * This function serves as a wrapper function for [[processDefSites]] in the sense that it - * processes multiple definition sites. Thus, it may throw an exception as well if an expression - * referenced by a definition site cannot be processed. The same rules as for [[processDefSite]] - * apply. - * - * @param defSites The definition sites to process. - * - * @return Returns a list of lists of [[StringConstancyInformation]]. Note that this function - * preserves the order of the given `defSites`, i.e., the first element in the result - * list corresponds to the first element in `defSites` and so on. If a site could not be - * processed, the list for that site will be the empty list. - */ - final def processDefSites(defSites: Array[Int]): List[List[StringConstancyInformation]] = - defSites.length match { - case 0 ⇒ List() - case 1 ⇒ List(processDefSite(defSites.head)) - case _ ⇒ defSites.filter(_ >= 0).map(processDefSite).toList - } + def processDefSite(defSite: Int): ProperPropertyComputationResult /** * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As @@ -198,7 +184,7 @@ object InterpretationHandler { * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], * [[StringConstancyType.APPEND]], and [[StringConstancyInformation.IntValue]]. */ - def getConstancyInformationForDynamicInt: StringConstancyInformation = + def getConstancyInfoForDynamicInt: StringConstancyInformation = StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, @@ -210,7 +196,7 @@ object InterpretationHandler { * That is, the returned element consists of the value [[StringConstancyLevel.DYNAMIC]], * [[StringConstancyType.APPEND]], and [[StringConstancyInformation.IntValue]]. */ - def getConstancyInformationForDynamicFloat: StringConstancyInformation = + def getConstancyInfoForDynamicFloat: StringConstancyInformation = StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, @@ -218,16 +204,16 @@ object InterpretationHandler { ) /** - * @return Returns a [[StringConstancyInformation]] element that describes a the result of a + * @return Returns a [[StringConstancyProperty]] element that describes the result of a * `replace` operation. That is, the returned element currently consists of the value * [[StringConstancyLevel.DYNAMIC]], [[StringConstancyType.REPLACE]], and * [[StringConstancyInformation.UnknownWordSymbol]]. */ - def getStringConstancyInformationForReplace: StringConstancyInformation = - StringConstancyInformation( + def getStringConstancyPropertyForReplace: StringConstancyProperty = + StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.REPLACE, StringConstancyInformation.UnknownWordSymbol - ) + )) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala index 83aee36017..d283ab7866 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala @@ -3,6 +3,8 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -33,7 +35,7 @@ class InterproceduralArrayInterpreter( /** * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { + override def interpret(instr: T): ProperPropertyComputationResult = { // TODO: Change from intra- to interprocedural val stmts = cfg.code.instructions val children = ListBuffer[StringConstancyInformation]() @@ -47,9 +49,9 @@ class InterproceduralArrayInterpreter( stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - sortedDefs.map { exprHandler.processDefSite }.foreach { - children.appendAll(_) - } + children.appendAll(sortedDefs.map { exprHandler.processDefSite }.map { + _.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) } // Process ArrayLoads sortedArrDeclUses.filter { @@ -59,9 +61,9 @@ class InterproceduralArrayInterpreter( } } foreach { f: Int ⇒ val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - defs.toArray.sorted.map { exprHandler.processDefSite }.foreach { - children.appendAll(_) - } + children.appendAll(defs.toArray.sorted.map { exprHandler.processDefSite }.map { + _.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) } } @@ -70,7 +72,9 @@ class InterproceduralArrayInterpreter( children.append(StringConstancyProperty.lowerBound.stringConstancyInformation) } - children.toList + Result(instr, StringConstancyProperty( + StringConstancyInformation.reduceMultiple(children.toList) + )) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala index 8d6daa2579..5d21b2d579 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala @@ -1,11 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetField import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -14,7 +14,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `InterproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this * implementation, there is currently only primitive support for fields, i.e., they are not analyzed - * but a constant [[StringConstancyInformation]] is returned (see [[interpret]] of this class). + * but a constant [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] + * is returned (see [[interpret]] of this class). * * @see [[AbstractStringInterpreter]] * @@ -30,17 +31,14 @@ class InterproceduralFieldInterpreter( /** * Currently, fields are not interpreted. Thus, this function always returns a list with a - * single element consisting of [[StringConstancyLevel.DYNAMIC]], - * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * single element consisting of + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]], + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]] and + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - // TODO: Change from intra- to interprocedural - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 16f83a540b..471793f4dc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -1,10 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -27,9 +29,13 @@ import org.opalj.tac.VirtualMethodCall * `InterproceduralInterpretationHandler` is responsible for processing expressions that are * relevant in order to determine which value(s) a string read operation might have. These * expressions usually come from the definitions sites of the variable of interest. + *

      + * For this interpretation handler used interpreters (concrete instances of + * [[AbstractStringInterpreter]]) can either return a final or intermediate result. * * @param cfg The control flow graph that underlies the program / method in which the expressions of * interest reside. + * * @author Patrick Mell */ class InterproceduralInterpretationHandler( @@ -44,16 +50,19 @@ class InterproceduralInterpretationHandler( *

      * @inheritdoc */ - override def processDefSite(defSite: Int): List[StringConstancyInformation] = { + override def processDefSite(defSite: Int): ProperPropertyComputationResult = { + // Without doing the following conversion, the following compile error will occur: "the + // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" + val e: Integer = defSite.toInt // Function parameters are not evaluated but regarded as unknown if (defSite < 0) { - return List(StringConstancyProperty.lowerBound.stringConstancyInformation) + return Result(e, StringConstancyProperty.lowerBound) } else if (processedDefSites.contains(defSite)) { - return List() + return Result(e, StringConstancyProperty.getNeutralElement) } processedDefSites.append(defSite) - stmts(defSite) match { + val result = stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ new StringConstInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: IntConst) ⇒ @@ -88,9 +97,10 @@ class InterproceduralInterpretationHandler( new InterproceduralNonVirtualMethodCallInterpreter( cfg, this, callees ).interpret(nvmc) - case _ ⇒ List() - + case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } + // Replace the entity of the result + Result(e, result.asInstanceOf[StringConstancyProperty]) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala index fe5fba5860..83a6a8d903 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,11 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -29,17 +29,14 @@ class InterproceduralNonVirtualFunctionCallInterpreter( /** * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns - * a list with a single element consisting of [[StringConstancyLevel.DYNAMIC]], - * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * a list with a single element consisting of + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]], + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]] and + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - // TODO: Change from intra- to interprocedural - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala index 38cb236fe4..6fc2d8c04e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -3,9 +3,12 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -41,30 +44,34 @@ class InterproceduralNonVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { - // TODO: Change from intra- to interprocedural - instr.name match { + override def interpret(instr: NonVirtualMethodCall[V]): ProperPropertyComputationResult = { + val prop = instr.name match { case "" ⇒ interpretInit(instr) - case _ ⇒ List() + case _ ⇒ StringConstancyProperty.getNeutralElement } + Result(instr, prop) } /** - * Processes an `<init>` method call. If it has no parameters, an empty list will be - * returned. Otherwise, only the very first parameter will be evaluated and its result returned - * (this is reasonable as both, [[StringBuffer]] and [[StringBuilder]], have only constructors - * with <= 0 arguments and only these are currently interpreted). + * Processes an `<init>` method call. If it has no parameters, + * [[StringConstancyProperty.getNeutralElement]] will be returned. Otherwise, only the very + * first parameter will be evaluated and its result returned (this is reasonable as both, + * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only + * these are currently interpreted). */ - private def interpretInit(init: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { + private def interpretInit(init: NonVirtualMethodCall[V]): StringConstancyProperty = { init.params.size match { - case 0 ⇒ List() - //List(StringConstancyInformation(StringConstancyLevel.CONSTANT, "")) + case 0 ⇒ StringConstancyProperty.getNeutralElement case _ ⇒ val scis = ListBuffer[StringConstancyInformation]() init.params.head.asVar.definedBy.foreach { ds ⇒ - scis.append(exprHandler.processDefSite(ds): _*) + val result = exprHandler.processDefSite(ds) + scis.append( + result.asInstanceOf[StringConstancyProperty].stringConstancyInformation + ) } - scis.toList + val reduced = StringConstancyInformation.reduceMultiple(scis.toList) + StringConstancyProperty(reduced) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala index cbf6a9046b..593318d36d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -1,11 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -31,17 +31,13 @@ class InterproceduralStaticFunctionCallInterpreter( /** * This function always returns a list with a single element consisting of - * [[StringConstancyLevel.DYNAMIC]], [[StringConstancyType.APPEND]], and - * [[StringConstancyInformation.UnknownWordSymbol]]. + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]], + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]], and + * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - // TODO: Change from intra- to interprocedural - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala index 850914b71a..a9e79f4360 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -56,19 +58,20 @@ class InterproceduralVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { - // TODO: Change from intra- to interprocedural - instr.name match { - case "append" ⇒ interpretAppendCall(instr).getOrElse(List()) + override def interpret(instr: T): ProperPropertyComputationResult = { + val property = instr.name match { + case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) case "replace" ⇒ interpretReplaceCall(instr) case _ ⇒ instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ - List(StringConstancyProperty.lowerBound.stringConstancyInformation) - case _ ⇒ List() + StringConstancyProperty.lowerBound + case _ ⇒ StringConstancyProperty.getNeutralElement } } + + Result(instr, property) } /** @@ -78,35 +81,35 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V] - ): Option[List[StringConstancyInformation]] = { - val receiverValues = receiverValuesOfAppendCall(appendCall) - val appendValue = valueOfAppendCall(appendCall) + ): StringConstancyProperty = { + val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation + val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation // The case can occur that receiver and append value are empty; although, it is // counter-intuitive, this case may occur if both, the receiver and the parameter, have been // processed before - if (receiverValues.isEmpty && appendValue.isEmpty) { - None + val sci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { + StringConstancyInformation.getNeutralElement } // It might be that we have to go back as much as to a New expression. As they do not // produce a result (= empty list), the if part - else if (receiverValues.isEmpty) { - Some(List(appendValue.get)) + else if (receiverSci.isTheNeutralElement) { + appendSci } // The append value might be empty, if the site has already been processed (then this // information will come from another StringConstancyInformation object - else if (appendValue.isEmpty) { - Some(receiverValues) + else if (appendSci.isTheNeutralElement) { + receiverSci } // Receiver and parameter information are available => Combine them else { - Some(receiverValues.map { nextSci ⇒ - StringConstancyInformation( - StringConstancyLevel.determineForConcat( - nextSci.constancyLevel, appendValue.get.constancyLevel - ), - StringConstancyType.APPEND, - nextSci.possibleStrings + appendValue.get.possibleStrings - ) - }) + StringConstancyInformation( + StringConstancyLevel.determineForConcat( + receiverSci.constancyLevel, appendSci.constancyLevel + ), + StringConstancyType.APPEND, + receiverSci.possibleStrings + appendSci.possibleStrings + ) } + + StringConstancyProperty(sci) } /** @@ -114,12 +117,15 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = + ): StringConstancyProperty = { // There might be several receivers, thus the map; from the processed sites, however, use // only the head as a single receiver interpretation will produce one element - call.receiver.asVar.definedBy.toArray.sorted.map( - exprHandler.processDefSite - ).filter(_.nonEmpty).map(_.head).toList + call.receiver.asVar.definedBy.toArray.sorted.map(exprHandler.processDefSite).map( + _.asInstanceOf[StringConstancyProperty] + ).filter { + !_.stringConstancyInformation.isTheNeutralElement + }.head + } /** * Determines the (string) value that was passed to a `String{Builder, Buffer}#append` method. @@ -127,48 +133,53 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V] - ): Option[StringConstancyInformation] = { + ): StringConstancyProperty = { val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append - val defSiteParamHead = param.definedBy.head - var value = exprHandler.processDefSite(defSiteParamHead) - // If defSiteParamHead points to a New, value will be the empty list. In that case, process + val defSiteHead = param.definedBy.head + var value = exprHandler.processDefSite(defSiteHead).asInstanceOf[StringConstancyProperty] + // If defSiteHead points to a New, value will be the empty list. In that case, process // the first use site (which is the call) - if (value.isEmpty) { + if (value.isTheNeutralElement) { value = exprHandler.processDefSite( - cfg.code.instructions(defSiteParamHead).asAssignment.targetVar.usedBy.toArray.min - ) + cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min + ).asInstanceOf[StringConstancyProperty] } - param.value.computationalType match { + + val sci = value.stringConstancyInformation + val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ // The value was already computed above; however, we need to check whether the // append takes an int value or a char (if it is a constant char, convert it) if (call.descriptor.parameterType(0).isCharType && - value.head.constancyLevel == StringConstancyLevel.CONSTANT) { - Some(value.head.copy( - possibleStrings = value.head.possibleStrings.toInt.toChar.toString - )) + sci.constancyLevel == StringConstancyLevel.CONSTANT) { + sci.copy( + possibleStrings = sci.possibleStrings.toInt.toChar.toString + ) } else { - Some(value.head) + sci } case ComputationalTypeFloat ⇒ - Some(InterpretationHandler.getConstancyInformationForDynamicFloat) + InterpretationHandler.getConstancyInfoForDynamicFloat // Otherwise, try to compute case _ ⇒ // It might be necessary to merge the values of the receiver and of the parameter - value.size match { - case 0 ⇒ None - case 1 ⇒ Some(value.head) - case _ ⇒ Some(StringConstancyInformation( - StringConstancyLevel.determineForConcat( - value.head.constancyLevel, value(1).constancyLevel - ), - StringConstancyType.APPEND, - value.head.possibleStrings + value(1).possibleStrings - )) - } + // value.size match { + // case 0 ⇒ None + // case 1 ⇒ Some(value.head) + // case _ ⇒ Some(StringConstancyInformation( + // StringConstancyLevel.determineForConcat( + // value.head.constancyLevel, value(1).constancyLevel + // ), + // StringConstancyType.APPEND, + // value.head.possibleStrings + value(1).possibleStrings + // )) + // } + sci } + + StringConstancyProperty(finalSci) } /** @@ -178,18 +189,18 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def interpretToStringCall( call: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = - exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + ): StringConstancyProperty = { + val result = exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + result.asInstanceOf[StringConstancyProperty] + } /** * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. - * Currently, this function simply approximates `replace` functions by returning a list with one - * element - the element currently is provided by - * [[InterpretationHandler.getStringConstancyInformationForReplace]]. + * (Currently, this function simply approximates `replace` functions by returning the lower + * bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall( instr: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = - List(InterpretationHandler.getStringConstancyInformationForReplace) + ): StringConstancyProperty = InterpretationHandler.getStringConstancyPropertyForReplace } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala index 745410946a..1a3a70c8dc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala @@ -1,11 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall @@ -42,14 +45,14 @@ class InterproceduralVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { - // TODO: Change from intra- to interprocedural - instr.name match { - case "setLength" ⇒ List(StringConstancyInformation( + override def interpret(instr: T): ProperPropertyComputationResult = { + val sci = instr.name match { + case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET - )) - case _ ⇒ List() + ) + case _ ⇒ StringConstancyInformation.getNeutralElement } + Result(instr, StringConstancyProperty(sci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala index 107dfa8722..257d63132e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala @@ -3,6 +3,8 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -31,7 +33,7 @@ class IntraproceduralArrayInterpreter( /** * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { + override def interpret(instr: T): ProperPropertyComputationResult = { val stmts = cfg.code.instructions val children = ListBuffer[StringConstancyInformation]() // Loop over all possible array values @@ -44,9 +46,10 @@ class IntraproceduralArrayInterpreter( stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - sortedDefs.map { exprHandler.processDefSite }.foreach { - children.appendAll(_) - } + children.appendAll(sortedDefs.map { exprHandler.processDefSite }.map { n ⇒ + val r = n.asInstanceOf[Result] + r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) } // Process ArrayLoads sortedArrDeclUses.filter { @@ -56,9 +59,10 @@ class IntraproceduralArrayInterpreter( } } foreach { f: Int ⇒ val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - defs.toArray.sorted.map { exprHandler.processDefSite }.foreach { - children.appendAll(_) - } + children.appendAll(defs.toArray.sorted.map { exprHandler.processDefSite }.map { n ⇒ + val r = n.asInstanceOf[Result] + r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) } } @@ -67,7 +71,11 @@ class IntraproceduralArrayInterpreter( children.append(StringConstancyProperty.lowerBound.stringConstancyInformation) } - children.toList + Result(instr, StringConstancyProperty( + StringConstancyInformation.reduceMultiple( + children.filter(!_.isTheNeutralElement).toList + ) + )) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala index e9b2bbe162..031582865d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetField import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -13,7 +13,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `IntraproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this * implementation, there is currently only primitive support for fields, i.e., they are not analyzed - * but a constant [[StringConstancyInformation]] is returned (see [[interpret]] of this class). + * but a constant [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] + * is returned (see [[interpret]] of this class). * * @see [[AbstractStringInterpreter]] * @@ -27,17 +28,12 @@ class IntraproceduralFieldInterpreter( override type T = GetField[V] /** - * Currently, fields are not interpreted. Thus, this function always returns a list with a - * single element consisting of [[StringConstancyLevel.DYNAMIC]], - * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * Fields are not suppoerted by this implementation. Thus, this function always returns a result + * containing [[StringConstancyProperty.lowerBound]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala index ecb6ef5624..5946503b8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetStatic import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -13,7 +13,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * The `IntraproceduralGetStaticInterpreter` is responsible for processing * [[org.opalj.tac.GetStatic]]s in an intraprocedural fashion. Thus, they are not analyzed but a - * fixed [[StringConstancyInformation]] is returned (see [[interpret]]). + * fixed [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] is returned + * (see [[interpret]]). * * @see [[AbstractStringInterpreter]] * @@ -27,17 +28,12 @@ class IntraproceduralGetStaticInterpreter( override type T = GetStatic /** - * Currently, this type is not interpreted. Thus, this function always returns a list with a - * single element consisting of [[StringConstancyLevel.DYNAMIC]], - * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * Currently, this type is not interpreted. Thus, this function always returns a result + * containing [[StringConstancyProperty.lowerBound]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala index 68db868e76..3c31e3ba99 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -1,9 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr @@ -25,6 +26,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are * relevant in order to determine which value(s) a string read operation might have. These * expressions usually come from the definitions sites of the variable of interest. + *

      + * For this interpretation handler it is crucial that all used interpreters (concrete instances of + * [[AbstractStringInterpreter]]) return a final computation result! * * @param cfg The control flow graph that underlies the program / method in which the expressions of * interest reside. @@ -39,16 +43,19 @@ class IntraproceduralInterpretationHandler( *

      * @inheritdoc */ - override def processDefSite(defSite: Int): List[StringConstancyInformation] = { + override def processDefSite(defSite: Int): ProperPropertyComputationResult = { + // Without doing the following conversion, the following compile error will occur: "the + // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" + val e: Integer = defSite.toInt // Function parameters are not evaluated but regarded as unknown if (defSite < 0) { - return List(StringConstancyProperty.lowerBound.stringConstancyInformation) + return Result(e, StringConstancyProperty.lowerBound) } else if (processedDefSites.contains(defSite)) { - return List() + return Result(e, StringConstancyProperty.getNeutralElement) } processedDefSites.append(defSite) - stmts(defSite) match { + val result: ProperPropertyComputationResult = stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ new StringConstInterpreter(cfg, this).interpret(expr) case Assignment(_, _, expr: IntConst) ⇒ @@ -75,9 +82,10 @@ class IntraproceduralInterpretationHandler( new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc) case nvmc: NonVirtualMethodCall[V] ⇒ new IntraproceduralNonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) - case _ ⇒ List() - + case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } + // Replace the entity of the result + Result(e, result.asInstanceOf[Result].finalEP.p) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala index 950a4d44eb..34d7c2ca0e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -20,23 +20,17 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V */ class IntraproceduralNonVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler, + exprHandler: IntraproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] /** - * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns - * a list with a single element consisting of [[StringConstancyLevel.DYNAMIC]], - * [[StringConstancyType.APPEND]] and [[StringConstancyInformation.UnknownWordSymbol]]. + * This function always returns a result that contains [[StringConstancyProperty.lowerBound]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala index 916feebc81..7428f623e2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala @@ -3,8 +3,11 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -35,33 +38,40 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * one is interpreted). * *

    - * For all other calls, an empty list will be returned at the moment. + * + * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will + * be returned. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { - instr.name match { + override def interpret(instr: NonVirtualMethodCall[V]): ProperPropertyComputationResult = { + val prop = instr.name match { case "" ⇒ interpretInit(instr) - case _ ⇒ List() + case _ ⇒ StringConstancyProperty.getNeutralElement } + Result(instr, prop) } /** - * Processes an `<init>` method call. If it has no parameters, an empty list will be - * returned. Otherwise, only the very first parameter will be evaluated and its result returned - * (this is reasonable as both, [[StringBuffer]] and [[StringBuilder]], have only constructors - * with <= 0 arguments and only these are currently interpreted). + * Processes an `<init>` method call. If it has no parameters, + * [[StringConstancyProperty.getNeutralElement]] will be returned. Otherwise, only the very + * first parameter will be evaluated and its result returned (this is reasonable as both, + * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only + * these are currently interpreted). */ - private def interpretInit(init: NonVirtualMethodCall[V]): List[StringConstancyInformation] = { + private def interpretInit(init: NonVirtualMethodCall[V]): StringConstancyProperty = { init.params.size match { - case 0 ⇒ List() - //List(StringConstancyInformation(StringConstancyLevel.CONSTANT, "")) + case 0 ⇒ StringConstancyProperty.getNeutralElement case _ ⇒ val scis = ListBuffer[StringConstancyInformation]() init.params.head.asVar.definedBy.foreach { ds ⇒ - scis.append(exprHandler.processDefSite(ds): _*) + val r = exprHandler.processDefSite(ds).asInstanceOf[Result] + scis.append( + r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + ) } - scis.toList + val reduced = StringConstancyInformation.reduceMultiple(scis.toList) + StringConstancyProperty(reduced) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala index 83c6fefa93..018da4b01d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala @@ -1,10 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -28,17 +28,11 @@ class IntraproceduralStaticFunctionCallInterpreter( override type T = StaticFunctionCall[V] /** - * This function always returns a list with a single element consisting of - * [[StringConstancyLevel.DYNAMIC]], [[StringConstancyType.APPEND]], and - * [[StringConstancyInformation.UnknownWordSymbol]]. + * This function always returns a result containing [[StringConstancyProperty.lowerBound]]. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala index 0b8c4f7134..43d045dda3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -50,22 +52,25 @@ class IntraproceduralVirtualFunctionCallInterpreter( * * * - * If none of the above-described cases match, an empty list will be returned. + * If none of the above-described cases match, a result containing + * [[StringConstancyProperty.getNeutralElement]] will be returned. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { - instr.name match { - case "append" ⇒ interpretAppendCall(instr).getOrElse(List()) + override def interpret(instr: T): ProperPropertyComputationResult = { + val property = instr.name match { + case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) case "replace" ⇒ interpretReplaceCall(instr) case _ ⇒ instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ - List(StringConstancyProperty.lowerBound.stringConstancyInformation) - case _ ⇒ List() + StringConstancyProperty.lowerBound + case _ ⇒ StringConstancyProperty.getNeutralElement } } + + Result(instr, property) } /** @@ -75,35 +80,35 @@ class IntraproceduralVirtualFunctionCallInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V] - ): Option[List[StringConstancyInformation]] = { - val receiverValues = receiverValuesOfAppendCall(appendCall) - val appendValue = valueOfAppendCall(appendCall) + ): StringConstancyProperty = { + val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation + val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation // The case can occur that receiver and append value are empty; although, it is // counter-intuitive, this case may occur if both, the receiver and the parameter, have been // processed before - if (receiverValues.isEmpty && appendValue.isEmpty) { - None + val sci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { + StringConstancyInformation.getNeutralElement } // It might be that we have to go back as much as to a New expression. As they do not // produce a result (= empty list), the if part - else if (receiverValues.isEmpty) { - Some(List(appendValue.get)) + else if (receiverSci.isTheNeutralElement) { + appendSci } // The append value might be empty, if the site has already been processed (then this // information will come from another StringConstancyInformation object - else if (appendValue.isEmpty) { - Some(receiverValues) + else if (appendSci.isTheNeutralElement) { + receiverSci } // Receiver and parameter information are available => Combine them else { - Some(receiverValues.map { nextSci ⇒ - StringConstancyInformation( - StringConstancyLevel.determineForConcat( - nextSci.constancyLevel, appendValue.get.constancyLevel - ), - StringConstancyType.APPEND, - nextSci.possibleStrings + appendValue.get.possibleStrings - ) - }) + StringConstancyInformation( + StringConstancyLevel.determineForConcat( + receiverSci.constancyLevel, appendSci.constancyLevel + ), + StringConstancyType.APPEND, + receiverSci.possibleStrings + appendSci.possibleStrings + ) } + + StringConstancyProperty(sci) } /** @@ -111,12 +116,17 @@ class IntraproceduralVirtualFunctionCallInterpreter( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = + ): StringConstancyProperty = { // There might be several receivers, thus the map; from the processed sites, however, use // only the head as a single receiver interpretation will produce one element - call.receiver.asVar.definedBy.toArray.sorted.map( - exprHandler.processDefSite - ).filter(_.nonEmpty).map(_.head).toList + val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds ⇒ + val r = exprHandler.processDefSite(ds).asInstanceOf[Result] + r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }.filter { sci ⇒ !sci.isTheNeutralElement } + val sci = if (scis.isEmpty) StringConstancyInformation.getNeutralElement else + scis.head + StringConstancyProperty(sci) + } /** * Determines the (string) value that was passed to a `String{Builder, Buffer}#append` method. @@ -124,48 +134,55 @@ class IntraproceduralVirtualFunctionCallInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V] - ): Option[StringConstancyInformation] = { + ): StringConstancyProperty = { val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append - val defSiteParamHead = param.definedBy.head - var value = exprHandler.processDefSite(defSiteParamHead) - // If defSiteParamHead points to a New, value will be the empty list. In that case, process + val defSiteHead = param.definedBy.head + var r = exprHandler.processDefSite(defSiteHead).asInstanceOf[Result] + var value = r.finalEP.p.asInstanceOf[StringConstancyProperty] + // If defSiteHead points to a New, value will be the empty list. In that case, process // the first use site (which is the call) - if (value.isEmpty) { - value = exprHandler.processDefSite( - cfg.code.instructions(defSiteParamHead).asAssignment.targetVar.usedBy.toArray.min - ) + if (value.isTheNeutralElement) { + r = exprHandler.processDefSite( + cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min + ).asInstanceOf[Result] + value = r.finalEP.p.asInstanceOf[StringConstancyProperty] } - param.value.computationalType match { + + val sci = value.stringConstancyInformation + val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ // The value was already computed above; however, we need to check whether the // append takes an int value or a char (if it is a constant char, convert it) if (call.descriptor.parameterType(0).isCharType && - value.head.constancyLevel == StringConstancyLevel.CONSTANT) { - Some(value.head.copy( - possibleStrings = value.head.possibleStrings.toInt.toChar.toString - )) + sci.constancyLevel == StringConstancyLevel.CONSTANT) { + sci.copy( + possibleStrings = sci.possibleStrings.toInt.toChar.toString + ) } else { - Some(value.head) + sci } case ComputationalTypeFloat ⇒ - Some(InterpretationHandler.getConstancyInformationForDynamicFloat) + InterpretationHandler.getConstancyInfoForDynamicFloat // Otherwise, try to compute case _ ⇒ // It might be necessary to merge the values of the receiver and of the parameter - value.size match { - case 0 ⇒ None - case 1 ⇒ Some(value.head) - case _ ⇒ Some(StringConstancyInformation( - StringConstancyLevel.determineForConcat( - value.head.constancyLevel, value(1).constancyLevel - ), - StringConstancyType.APPEND, - value.head.possibleStrings + value(1).possibleStrings - )) - } + // value.size match { + // case 0 ⇒ None + // case 1 ⇒ Some(value.head) + // case _ ⇒ Some(StringConstancyInformation( + // StringConstancyLevel.determineForConcat( + // value.head.constancyLevel, value(1).constancyLevel + // ), + // StringConstancyType.APPEND, + // value.head.possibleStrings + value(1).possibleStrings + // )) + // } + sci } + + StringConstancyProperty(finalSci) } /** @@ -175,18 +192,18 @@ class IntraproceduralVirtualFunctionCallInterpreter( */ private def interpretToStringCall( call: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = - exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + ): StringConstancyProperty = { + val r = exprHandler.processDefSite(call.receiver.asVar.definedBy.head).asInstanceOf[Result] + r.finalEP.p.asInstanceOf[StringConstancyProperty] + } /** * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. - * Currently, this function simply approximates `replace` functions by returning a list with one - * element - the element currently is provided by - * [[InterpretationHandler.getStringConstancyInformationForReplace]]. + * (Currently, this function simply approximates `replace` functions by returning the lower + * bound of [[StringConstancyProperty]]). */ private def interpretReplaceCall( instr: VirtualFunctionCall[V] - ): List[StringConstancyInformation] = - List(InterpretationHandler.getStringConstancyInformationForReplace) + ): StringConstancyProperty = InterpretationHandler.getStringConstancyPropertyForReplace } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala index 53e0be513b..58c25bfa47 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala @@ -1,10 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall @@ -36,17 +39,20 @@ class IntraproceduralVirtualMethodCallInterpreter( * a reset mechanism. * * - * For all other calls, an empty list will be returned. + * + * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will + * be returned. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = { - instr.name match { - case "setLength" ⇒ List(StringConstancyInformation( + override def interpret(instr: T): ProperPropertyComputationResult = { + val sci = instr.name match { + case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET - )) - case _ ⇒ List() + ) + case _ ⇒ StringConstancyInformation.getNeutralElement } + Result(instr, StringConstancyProperty(sci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala index b0652e77b9..0ad19b8342 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala @@ -1,8 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.New import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -27,11 +29,12 @@ class NewInterpreter( /** * [[New]] expressions do not carry any relevant information in this context (as the initial * values are not set in a [[New]] expressions but, e.g., in - * [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, this implementation always returns an - * empty list. + * [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, this implementation always returns a + * Result containing [[StringConstancyProperty.getNeutralElement]] * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = List() + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala index 2a5289887e..2024148503 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala @@ -1,10 +1,13 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.StringConst import org.opalj.tac.TACStmts @@ -34,11 +37,11 @@ class StringConstInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): List[StringConstancyInformation] = - List(StringConstancyInformation( + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value - )) + ))) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index f371699cc8..42fcf5819a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -3,6 +3,7 @@ package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.properties.StringTree import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -11,6 +12,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringTreeCond import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.br.fpcf.properties.string_definition.StringTreeOr import org.opalj.br.fpcf.properties.string_definition.StringTreeRepetition +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -39,20 +41,27 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { ): Option[StringTree] = { subpath match { case fpe: FlatPathElement ⇒ - val sciList = if (fpe2Sci.contains(fpe.element)) List(fpe2Sci(fpe.element)) else - exprHandler.processDefSite(fpe.element) - sciList.length match { - case 0 ⇒ None - case 1 ⇒ Some(StringTreeConst(sciList.head)) - case _ ⇒ - val treeElements = ListBuffer[StringTree]() - treeElements.appendAll(sciList.map(StringTreeConst).to[ListBuffer]) - if (treeElements.nonEmpty) { - Some(StringTreeOr(treeElements)) - } else { - None - } + val sci = if (fpe2Sci.contains(fpe.element)) fpe2Sci(fpe.element) else { + val r = exprHandler.processDefSite(fpe.element).asInstanceOf[Result] + r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } + if (sci.isTheNeutralElement) { + None + } else { + Some(StringTreeConst(sci)) + } + // sciList.length match { + // case 0 ⇒ None + // case 1 ⇒ Some(StringTreeConst(sciList.head)) + // case _ ⇒ + // val treeElements = ListBuffer[StringTree]() + // treeElements.appendAll(sciList.map(StringTreeConst).to[ListBuffer]) + // if (treeElements.nonEmpty) { + // Some(StringTreeOr(treeElements)) + // } else { + // None + // } + // } case npe: NestedPathElement ⇒ if (npe.elementType.isDefined) { npe.elementType.get match { From 77df34cab1cfe81bd50cb46755c192fc0e19a20a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 29 Jan 2019 20:28:17 +0100 Subject: [PATCH 142/316] Added the interprocedural counterpart. --- .../InterproceduralGetStaticInterpreter.scala | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala new file mode 100644 index 0000000000..a2e38e5b92 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala @@ -0,0 +1,37 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.tac.GetStatic +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `InterproceduralGetStaticInterpreter` is responsible for processing + * [[org.opalj.tac.GetStatic]]s in an interprocedural fashion. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class InterproceduralGetStaticInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: IntraproceduralInterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = GetStatic + + /** + * Currently, this type is not interpreted. Thus, this function always returns a result + * containing [[StringConstancyProperty.lowerBound]]. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty.lowerBound) + +} \ No newline at end of file From d6005cc131d4b5c08265bf9b7608eb8849125109 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 07:36:38 +0100 Subject: [PATCH 143/316] Slightly changed the interface of AbstractStringInterpreter (which, again, resulted in changes in many files). --- .../properties/StringConstancyProperty.scala | 10 ++ .../InterproceduralStringAnalysis.scala | 132 +++++++++++------- .../string_analysis/LocalStringAnalysis.scala | 8 +- .../AbstractStringInterpreter.scala | 6 +- .../BinaryExprInterpreter.scala | 4 +- .../IntegerValueInterpreter.scala | 4 +- .../InterproceduralArrayInterpreter.scala | 4 +- .../InterproceduralFieldInterpreter.scala | 4 +- .../InterproceduralGetStaticInterpreter.scala | 4 +- ...InterproceduralInterpretationHandler.scala | 53 ++++--- ...ralNonVirtualFunctionCallInterpreter.scala | 82 ++++++++++- ...duralNonVirtualMethodCallInterpreter.scala | 9 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...eduralVirtualFunctionCallInterpreter.scala | 23 ++- ...oceduralVirtualMethodCallInterpreter.scala | 4 +- .../IntraproceduralArrayInterpreter.scala | 4 +- .../IntraproceduralFieldInterpreter.scala | 4 +- .../IntraproceduralGetStaticInterpreter.scala | 4 +- ...IntraproceduralInterpretationHandler.scala | 36 +++-- ...ralNonVirtualFunctionCallInterpreter.scala | 4 +- ...duralNonVirtualMethodCallInterpreter.scala | 6 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...eduralVirtualFunctionCallInterpreter.scala | 4 +- ...oceduralVirtualMethodCallInterpreter.scala | 4 +- .../interpretation/NewInterpreter.scala | 6 +- .../StringConstInterpreter.scala | 4 +- .../preprocessing/PathTransformer.scala | 51 +++---- 27 files changed, 331 insertions(+), 151 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index a6b5b0fe27..cb4b073675 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -3,10 +3,12 @@ package org.opalj.br.fpcf.properties import org.opalj.fpcf.Entity import org.opalj.fpcf.FallbackReason +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType @@ -54,6 +56,14 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta stringConstancyInformation: StringConstancyInformation ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) + /** + * Extracts a [[Result]] from the geiven `ppcr` and returns its property as an instance of this + * class. + */ + def extractFromPPCR(ppcr: ProperPropertyComputationResult): StringConstancyProperty = { + ppcr.asInstanceOf[Result].asInstanceOf[StringConstancyProperty] + } + /** * @return Returns the / a neutral [[StringConstancyProperty]] element, i.e., an element for * which [[StringConstancyProperty.isTheNeutralElement]] is `true`. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 1b5a42fe15..03ddc14da7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -38,10 +38,32 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathEleme import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.ExprStmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath +/** + * This class is to be used to store state information that are required at a later point in + * time during the analysis, e.g., due to the fact that another analysis had to be triggered to + * have all required information ready for a final result. + */ +case class ComputationState( + // The lean path that was computed + var computedLeanPath: Option[Path], + // The control flow graph on which the computedLeanPath is based + cfg: CFG[Stmt[V], TACStmts[V]], + // + var callees: Option[Callees] = None +) { + // If not empty, this very routine can only produce an intermediate result + // TODO: The value must be a list as one entity can have multiple dependees! + val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() + // A mapping from values of FlatPathElements to StringConstancyInformation + val fpe2sci: mutable.Map[Int, StringConstancyInformation] = mutable.Map() +} + /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. @@ -57,21 +79,8 @@ class InterproceduralStringAnalysis( private var declaredMethods: DeclaredMethods = _ - /** - * This class is to be used to store state information that are required at a later point in - * time during the analysis, e.g., due to the fact that another analysis had to be triggered to - * have all required information ready for a final result. - */ - private case class ComputationState( - // The lean path that was computed - computedLeanPath: Path, - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - var2IndexMapping: mutable.Map[V, Int], - // A mapping from values of FlatPathElements to StringConstancyInformation - fpe2sci: mutable.Map[Int, StringConstancyInformation], - // The control flow graph on which the computedLeanPath is based - cfg: CFG[Stmt[V], TACStmts[V]] - ) + // state will be set to a non-null value in "determinePossibleStrings" + var state: ComputationState = _ def analyze(data: P): ProperPropertyComputationResult = { declaredMethods = project.get(DeclaredMethodsKey) @@ -101,6 +110,7 @@ class InterproceduralStringAnalysis( val tacProvider = p.get(SimpleTACAIKey) val cfg = tacProvider(data._2).cfg val stmts = cfg.code.instructions + state = ComputationState(None, cfg, Some(callees)) val uvar = data._1 val defSites = uvar.definedBy.toArray.sorted @@ -111,13 +121,6 @@ class InterproceduralStringAnalysis( } val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) - // If not empty, this very routine can only produce an intermediate result - val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() - // state will be set to a non-null value if this analysis needs to call other analyses / - // itself; only in the case it calls itself, will state be used, thus, it is valid to - // initialize it with null - var state: ComputationState = null - val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) @@ -134,37 +137,60 @@ class InterproceduralStringAnalysis( if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar ⇒ val toAnalyze = (nextVar, data._2) - val fpe2sci = mutable.Map[Int, StringConstancyInformation]() - state = ComputationState(leanPaths, dependentVars, fpe2sci, cfg) + state.computedLeanPath = Some(leanPaths) + dependentVars.foreach { case (k, v) ⇒ state.var2IndexMapping(k) = v } val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ - return processFinalP(data, callees, dependees.values, state, ep.e, p) + return processFinalP(data, callees, state, ep.e, p) case _ ⇒ - dependees.put(toAnalyze, ep) + if (!state.dependees.contains(toAnalyze)) { + state.dependees(toAnalyze) = ListBuffer() + } + state.dependees(toAnalyze).append(ep) } } } else { - sci = new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true) + val interpretationHandler = InterproceduralInterpretationHandler( + cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) + ) + sci = new PathTransformer( + interpretationHandler + ).pathToStringTree(leanPaths).reduce(true) } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val interHandler = IntraproceduralInterpretationHandler(cfg) - sci = StringConstancyInformation.reduceMultiple( - uvar.definedBy.toArray.sorted.map { ds ⇒ - val nextResult = interHandler.processDefSite(ds) - nextResult.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }.toList + val interHandler = InterproceduralInterpretationHandler( + cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) ) + val results = uvar.definedBy.toArray.sorted.map { ds ⇒ + (ds, interHandler.processDefSite(ds)) + } + val interimResults = results.filter(!_._2.isInstanceOf[Result]).map { r ⇒ + (r._1, r._2.asInstanceOf[InterimResult[StringConstancyProperty]]) + } + if (interimResults.isEmpty) { + // All results are available => Prepare the final result + sci = StringConstancyInformation.reduceMultiple( + results.map { + case (_, r) ⇒ + val p = r.asInstanceOf[Result].finalEP.p + p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }.toList + ) + } + // No need to cover the else branch: interimResults.nonEmpty => dependees were added to + // state.dependees, i.e., the if that checks whether state.dependees is non-empty will + // always be true (thus, the value of "sci" does not matter) } - if (dependees.nonEmpty) { + if (state.dependees.nonEmpty) { InterimResult( data, StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, - dependees.values, - continuation(data, callees, dependees.values, state) + state.dependees.values.flatten, + continuation(data, callees, state.dependees.values.flatten, state) ) } else { Result(data, StringConstancyProperty(sci)) @@ -188,12 +214,11 @@ class InterproceduralStringAnalysis( * [[org.opalj.fpcf.FinalP]]. */ private def processFinalP( - data: P, - callees: Callees, - dependees: Iterable[EOptionP[Entity, Property]], - state: ComputationState, - e: Entity, - p: Property + data: P, + callees: Callees, + state: ComputationState, + e: Entity, + p: Property ): ProperPropertyComputationResult = { // Add mapping information (which will be used for computing the final result) val retrievedProperty = p.asInstanceOf[StringConstancyProperty] @@ -201,11 +226,22 @@ class InterproceduralStringAnalysis( state.fpe2sci.put(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) // No more dependees => Return the result for this analysis run - val remDependees = dependees.filter(_.e != e) + state.dependees.foreach { case (k, v) ⇒ state.dependees(k) = v.filter(_.e != e) } + val remDependees = state.dependees.values.flatten if (remDependees.isEmpty) { - val finalSci = new PathTransformer(state.cfg).pathToStringTree( - state.computedLeanPath, state.fpe2sci.toMap - ).reduce(true) + // This is the case if the string information stems from a String{Builder, Buffer} + val finalSci = if (state.computedLeanPath.isDefined) { + val interpretationHandler = InterproceduralInterpretationHandler( + state.cfg, ps, declaredMethods, state, + continuation(data, callees, List(), state) + ) + new PathTransformer(interpretationHandler).pathToStringTree( + state.computedLeanPath.get, state.fpe2sci.toMap + ).reduce(true) + } else { + // This is the case if the string information stems from a String variable + currentSci + } Result(data, StringConstancyProperty(finalSci)) } else { InterimResult( @@ -233,7 +269,7 @@ class InterproceduralStringAnalysis( dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case FinalP(p) ⇒ processFinalP(data, callees, dependees, state, eps.e, p) + case FinalP(p) ⇒ processFinalP(data, callees, state, eps.e, p) case InterimLUBP(lb, ub) ⇒ InterimResult( data, lb, ub, dependees, continuation(data, callees, dependees, state) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala index a40a736455..3df2aebf13 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala @@ -122,7 +122,10 @@ class LocalStringAnalysis( } } } else { - sci = new PathTransformer(cfg).pathToStringTree(leanPaths).reduce(true) + val interpretationHandler = IntraproceduralInterpretationHandler(cfg) + sci = new PathTransformer( + interpretationHandler + ).pathToStringTree(leanPaths).reduce(true) } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { @@ -167,7 +170,8 @@ class LocalStringAnalysis( // No more dependees => Return the result for this analysis run val remDependees = dependees.filter(_.e != e) if (remDependees.isEmpty) { - val finalSci = new PathTransformer(state.cfg).pathToStringTree( + val interpretationHandler = IntraproceduralInterpretationHandler(state.cfg) + val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) Result(data, StringConstancyProperty(finalSci)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 4c5be67824..2c5794a6b6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -31,6 +31,10 @@ abstract class AbstractStringInterpreter( * @param instr The instruction that is to be interpreted. It is the responsibility of * implementations to make sure that an instruction is properly and comprehensively * evaluated. + * @param defSite The definition site that corresponds to the given instruction. `defSite` is + * not necessary for processing `instr`, however, may be used, e.g., for + * housekeeping purposes. Thus, concrete implementations should indicate whether + * this value is of importance for (further) processing. * @return The interpreted instruction. A neutral StringConstancyProperty contained in the * result indicates that an instruction was not / could not be interpreted (e.g., * because it is not supported or it was processed before). @@ -40,6 +44,6 @@ abstract class AbstractStringInterpreter( * the definition site, this function returns the interpreted instruction as entity. * Thus, the entity needs to be replaced by the calling client. */ - def interpret(instr: T): ProperPropertyComputationResult + def interpret(instr: T, defSite: Int): ProperPropertyComputationResult } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala index 28927d07ee..ba4e2dda8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala @@ -40,9 +40,11 @@ class BinaryExprInterpreter( * For all other expressions, a result containing [[StringConstancyProperty.getNeutralElement]] * will be returned. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val sci = instr.cTpe match { case ComputationalTypeInt ⇒ InterpretationHandler.getConstancyInfoForDynamicInt case ComputationalTypeFloat ⇒ InterpretationHandler.getConstancyInfoForDynamicFloat diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala index 350ee89c88..4e1cb3ac7b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala @@ -30,9 +30,11 @@ class IntegerValueInterpreter( override type T = IntConst /** + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala index d283ab7866..75682c61c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala @@ -33,9 +33,11 @@ class InterproceduralArrayInterpreter( override type T = ArrayLoad[V] /** + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { // TODO: Change from intra- to interprocedural val stmts = cfg.code.instructions val children = ListBuffer[StringConstancyInformation]() diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala index 5d21b2d579..2693c03119 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala @@ -36,9 +36,11 @@ class InterproceduralFieldInterpreter( * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]] and * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala index a2e38e5b92..9d7d929bf6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala @@ -29,9 +29,11 @@ class InterproceduralGetStaticInterpreter( * Currently, this type is not interpreted. Thus, this function always returns a result * containing [[StringConstancyProperty.lowerBound]]. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 471793f4dc..43997268c9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -1,12 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -24,6 +24,7 @@ import org.opalj.tac.StaticFunctionCall import org.opalj.tac.StringConst import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -42,12 +43,14 @@ class InterproceduralInterpretationHandler( cfg: CFG[Stmt[V], TACStmts[V]], ps: PropertyStore, declaredMethods: DeclaredMethods, - callees: Callees + state: ComputationState, + c: ProperOnUpdateContinuation ) extends InterpretationHandler(cfg) { /** * Processed the given definition site in an interprocedural fashion. *

    + * * @inheritdoc */ override def processDefSite(defSite: Int): ProperPropertyComputationResult = { @@ -62,45 +65,52 @@ class InterproceduralInterpretationHandler( } processedDefSites.append(defSite) - val result = stmts(defSite) match { + val callees = state.callees.get + // TODO: Refactor by making the match return a concrete instance of + // AbstractStringInterpreter on which 'interpret' is the called only once + stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ - new StringConstInterpreter(cfg, this).interpret(expr) + new StringConstInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: IntConst) ⇒ - new IntegerValueInterpreter(cfg, this).interpret(expr) + new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new InterproceduralArrayInterpreter(cfg, this, callees).interpret(expr) + new InterproceduralArrayInterpreter(cfg, this, callees).interpret(expr, defSite) case Assignment(_, _, expr: New) ⇒ - new NewInterpreter(cfg, this).interpret(expr) + new NewInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ new InterproceduralVirtualFunctionCallInterpreter( cfg, this, callees - ).interpret(expr) + ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ - new InterproceduralStaticFunctionCallInterpreter(cfg, this, callees).interpret(expr) + new InterproceduralStaticFunctionCallInterpreter( + cfg, this, callees + ).interpret(expr, defSite) case Assignment(_, _, expr: BinaryExpr[V]) ⇒ - new BinaryExprInterpreter(cfg, this).interpret(expr) + new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ new InterproceduralNonVirtualFunctionCallInterpreter( - cfg, this, callees - ).interpret(expr) + cfg, this, ps, state, declaredMethods, c + ).interpret(expr, defSite) case Assignment(_, _, expr: GetField[V]) ⇒ - new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr) + new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ new InterproceduralVirtualFunctionCallInterpreter( cfg, this, callees - ).interpret(expr) + ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ - new InterproceduralStaticFunctionCallInterpreter(cfg, this, callees).interpret(expr) + new InterproceduralStaticFunctionCallInterpreter( + cfg, this, callees + ).interpret(expr, defSite) case vmc: VirtualMethodCall[V] ⇒ - new InterproceduralVirtualMethodCallInterpreter(cfg, this, callees).interpret(vmc) + new InterproceduralVirtualMethodCallInterpreter( + cfg, this, callees + ).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] ⇒ new InterproceduralNonVirtualMethodCallInterpreter( cfg, this, callees - ).interpret(nvmc) + ).interpret(nvmc, defSite) case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } - // Replace the entity of the result - Result(e, result.asInstanceOf[StringConstancyProperty]) } } @@ -114,9 +124,10 @@ object InterproceduralInterpretationHandler { cfg: CFG[Stmt[V], TACStmts[V]], ps: PropertyStore, declaredMethods: DeclaredMethods, - callees: Callees + state: ComputationState, + c: ProperOnUpdateContinuation ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - cfg, ps, declaredMethods, callees + cfg, ps, declaredMethods, state, c ) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala index 83a6a8d903..94bec79303 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,15 +1,28 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEOptionP +import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.Method import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.ReturnValue +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState /** * The `InterproceduralNonVirtualFunctionCallInterpreter` is responsible for processing @@ -20,13 +33,31 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V * @author Patrick Mell */ class InterproceduralNonVirtualFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: ComputationState, + declaredMethods: DeclaredMethods, + c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] + def getTACAI( + m: Method, dependees: ListBuffer[SomeEOptionP] + ): Option[TACode[TACMethodParameter, V]] = { + val tacai = ps(m, TACAI.key) + if (tacai.hasUBP) { + tacai.ub.tac + } else { + if (!state.dependees.contains(m)) { + state.dependees(m) = ListBuffer() + } + state.dependees(m).append(tacai) + None + } + } + /** * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns * a list with a single element consisting of @@ -34,9 +65,48 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]] and * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + val dependees = ListBuffer[SomeEOptionP]() + val m = declaredMethods.declaredMethods.find(_.name == instr.name).get.definedMethod + val tac = getTACAI(m, dependees) + if (tac.isDefined) { + // TAC available => Get return UVar and start the string analysis + val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get + val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + val entity = (uvar, m) + + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case FinalEP(e, p) ⇒ + Result(e, p) + case _ ⇒ + if (!state.dependees.contains(m)) { + state.dependees(m) = ListBuffer() + } + state.dependees(m).append(eps) + state.var2IndexMapping(uvar) = defSite + InterimResult( + entity, + StringConstancyProperty.lowerBound, + StringConstancyProperty.upperBound, + List(), + c + ) + } + } else { + // No TAC => Register dependee and continue + InterimResult( + m, + StringConstancyProperty.lowerBound, + StringConstancyProperty.upperBound, + dependees, + c + ) + } + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala index 6fc2d8c04e..968e6bbe8a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -42,9 +42,13 @@ class InterproceduralNonVirtualMethodCallInterpreter( * * For all other calls, an empty list will be returned at the moment. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: NonVirtualMethodCall[V]): ProperPropertyComputationResult = { + override def interpret( + instr: NonVirtualMethodCall[V], defSite: Int + ): ProperPropertyComputationResult = { val prop = instr.name match { case "" ⇒ interpretInit(instr) case _ ⇒ StringConstancyProperty.getNeutralElement @@ -66,8 +70,9 @@ class InterproceduralNonVirtualMethodCallInterpreter( val scis = ListBuffer[StringConstancyInformation]() init.params.head.asVar.definedBy.foreach { ds ⇒ val result = exprHandler.processDefSite(ds) + val prop = result.asInstanceOf[Result].finalEP.p scis.append( - result.asInstanceOf[StringConstancyProperty].stringConstancyInformation + prop.asInstanceOf[StringConstancyProperty].stringConstancyInformation ) } val reduced = StringConstancyInformation.reduceMultiple(scis.toList) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala index 593318d36d..a57b59ae86 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -35,9 +35,11 @@ class InterproceduralStaticFunctionCallInterpreter( * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]], and * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala index a9e79f4360..5b15dac1ad 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala @@ -56,9 +56,11 @@ class InterproceduralVirtualFunctionCallInterpreter( * * If none of the above-described cases match, an empty list will be returned. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val property = instr.name match { case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) @@ -120,11 +122,16 @@ class InterproceduralVirtualFunctionCallInterpreter( ): StringConstancyProperty = { // There might be several receivers, thus the map; from the processed sites, however, use // only the head as a single receiver interpretation will produce one element - call.receiver.asVar.definedBy.toArray.sorted.map(exprHandler.processDefSite).map( - _.asInstanceOf[StringConstancyProperty] + val scis = call.receiver.asVar.definedBy.toArray.sorted.map(exprHandler.processDefSite).map( + _.asInstanceOf[Result].finalEP.p.asInstanceOf[StringConstancyProperty] ).filter { !_.stringConstancyInformation.isTheNeutralElement - }.head + } + if (scis.isEmpty) { + StringConstancyProperty.getNeutralElement + } else { + scis.head + } } /** @@ -137,13 +144,15 @@ class InterproceduralVirtualFunctionCallInterpreter( val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append val defSiteHead = param.definedBy.head - var value = exprHandler.processDefSite(defSiteHead).asInstanceOf[StringConstancyProperty] + var value = StringConstancyProperty.extractFromPPCR( + exprHandler.processDefSite(defSiteHead) + ) // If defSiteHead points to a New, value will be the empty list. In that case, process // the first use site (which is the call) if (value.isTheNeutralElement) { - value = exprHandler.processDefSite( + value = StringConstancyProperty.extractFromPPCR(exprHandler.processDefSite( cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min - ).asInstanceOf[StringConstancyProperty] + )) } val sci = value.stringConstancyInformation diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala index 1a3a70c8dc..4c6747d34f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala @@ -43,9 +43,11 @@ class InterproceduralVirtualMethodCallInterpreter( * * For all other calls, an empty list will be returned. * + * @note For this implementation, `defSite` plays a role! + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val sci = instr.name match { case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala index 257d63132e..e533e06494 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala @@ -31,9 +31,11 @@ class IntraproceduralArrayInterpreter( override type T = ArrayLoad[V] /** + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val stmts = cfg.code.instructions val children = ListBuffer[StringConstancyInformation]() // Loop over all possible array values diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala index 031582865d..4a6f262a0c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala @@ -31,9 +31,11 @@ class IntraproceduralFieldInterpreter( * Fields are not suppoerted by this implementation. Thus, this function always returns a result * containing [[StringConstancyProperty.lowerBound]]. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala index 5946503b8c..627a611481 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala @@ -31,9 +31,11 @@ class IntraproceduralGetStaticInterpreter( * Currently, this type is not interpreted. Thus, this function always returns a result * containing [[StringConstancyProperty.lowerBound]]. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala index 3c31e3ba99..14b57a2aa1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -55,33 +55,43 @@ class IntraproceduralInterpretationHandler( } processedDefSites.append(defSite) + // TODO: Refactor by making the match return a concrete instance of + // AbstractStringInterpreter on which 'interpret' is the called only once val result: ProperPropertyComputationResult = stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ - new StringConstInterpreter(cfg, this).interpret(expr) + new StringConstInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: IntConst) ⇒ - new IntegerValueInterpreter(cfg, this).interpret(expr) + new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new IntraproceduralArrayInterpreter(cfg, this).interpret(expr) + new IntraproceduralArrayInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: New) ⇒ - new NewInterpreter(cfg, this).interpret(expr) + new NewInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + new IntraproceduralVirtualFunctionCallInterpreter( + cfg, this + ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ - new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr) + new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: BinaryExpr[V]) ⇒ - new BinaryExprInterpreter(cfg, this).interpret(expr) + new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ - new IntraproceduralNonVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + new IntraproceduralNonVirtualFunctionCallInterpreter( + cfg, this + ).interpret(expr, defSite) case Assignment(_, _, expr: GetField[V]) ⇒ - new IntraproceduralFieldInterpreter(cfg, this).interpret(expr) + new IntraproceduralFieldInterpreter(cfg, this).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ - new IntraproceduralVirtualFunctionCallInterpreter(cfg, this).interpret(expr) + new IntraproceduralVirtualFunctionCallInterpreter( + cfg, this + ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ - new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr) + new IntraproceduralStaticFunctionCallInterpreter(cfg, this).interpret(expr, defSite) case vmc: VirtualMethodCall[V] ⇒ - new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc) + new IntraproceduralVirtualMethodCallInterpreter(cfg, this).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] ⇒ - new IntraproceduralNonVirtualMethodCallInterpreter(cfg, this).interpret(nvmc) + new IntraproceduralNonVirtualMethodCallInterpreter( + cfg, this + ).interpret(nvmc, defSite) case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } // Replace the entity of the result diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala index 34d7c2ca0e..45a2cf9a35 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala @@ -28,9 +28,11 @@ class IntraproceduralNonVirtualFunctionCallInterpreter( /** * This function always returns a result that contains [[StringConstancyProperty.lowerBound]]. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala index 7428f623e2..fb16e24311 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala @@ -42,9 +42,13 @@ class IntraproceduralNonVirtualMethodCallInterpreter( * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will * be returned. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: NonVirtualMethodCall[V]): ProperPropertyComputationResult = { + override def interpret( + instr: NonVirtualMethodCall[V], defSite: Int + ): ProperPropertyComputationResult = { val prop = instr.name match { case "" ⇒ interpretInit(instr) case _ ⇒ StringConstancyProperty.getNeutralElement diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala index 018da4b01d..346a241c73 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala @@ -30,9 +30,11 @@ class IntraproceduralStaticFunctionCallInterpreter( /** * This function always returns a result containing [[StringConstancyProperty.lowerBound]]. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.lowerBound) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala index 43d045dda3..09ab919d7b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -55,9 +55,11 @@ class IntraproceduralVirtualFunctionCallInterpreter( * If none of the above-described cases match, a result containing * [[StringConstancyProperty.getNeutralElement]] will be returned. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val property = instr.name match { case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala index 58c25bfa47..69bed8020a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala @@ -43,9 +43,11 @@ class IntraproceduralVirtualMethodCallInterpreter( * For all other calls, a result containing [[StringConstancyProperty.getNeutralElement]] will * be returned. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val sci = instr.name match { case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala index 0ad19b8342..40f9ce7146 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala @@ -30,11 +30,13 @@ class NewInterpreter( * [[New]] expressions do not carry any relevant information in this context (as the initial * values are not set in a [[New]] expressions but, e.g., in * [[org.opalj.tac.NonVirtualMethodCall]]s). Consequently, this implementation always returns a - * Result containing [[StringConstancyProperty.getNeutralElement]] + * Result containing [[StringConstancyProperty.getNeutralElement]]. + * + * @note For this implementation, `defSite` does not play a role. * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala index 2024148503..1142359fb9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala @@ -35,9 +35,11 @@ class StringConstInterpreter( * [[StringConstancyLevel.CONSTANT]] [[StringConstancyInformation]] element holding the * stringified value. * + * @note For this implementation, `defSite` does not play a role. + * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = Result(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 42fcf5819a..6cce173c88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -4,7 +4,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Result -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.properties.StringTree import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -13,10 +12,7 @@ import org.opalj.br.fpcf.properties.string_definition.StringTreeConst import org.opalj.br.fpcf.properties.string_definition.StringTreeOr import org.opalj.br.fpcf.properties.string_definition.StringTreeRepetition import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * [[PathTransformer]] is responsible for transforming a [[Path]] into another representation, such @@ -25,13 +21,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Intraprocedura * refer to the underlying control flow graph. If this is no longer the case, create a new instance * of this class with the corresponding (new) `cfg?`. * - * @param cfg Objects of this class require a control flow graph that is used for transformations. + * @param interpretationHandler An concrete instance of [[InterpretationHandler]] that is used to + * process expressions / definition sites. * * @author Patrick Mell */ -class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { - - private val exprHandler = IntraproceduralInterpretationHandler(cfg) +class PathTransformer(val interpretationHandler: InterpretationHandler) { /** * Accumulator function for transforming a path into a StringTree element. @@ -42,7 +37,7 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { subpath match { case fpe: FlatPathElement ⇒ val sci = if (fpe2Sci.contains(fpe.element)) fpe2Sci(fpe.element) else { - val r = exprHandler.processDefSite(fpe.element).asInstanceOf[Result] + val r = interpretationHandler.processDefSite(fpe.element).asInstanceOf[Result] r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } if (sci.isTheNeutralElement) { @@ -50,18 +45,6 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { } else { Some(StringTreeConst(sci)) } - // sciList.length match { - // case 0 ⇒ None - // case 1 ⇒ Some(StringTreeConst(sciList.head)) - // case _ ⇒ - // val treeElements = ListBuffer[StringTree]() - // treeElements.appendAll(sciList.map(StringTreeConst).to[ListBuffer]) - // if (treeElements.nonEmpty) { - // Some(StringTreeOr(treeElements)) - // } else { - // None - // } - // } case npe: NestedPathElement ⇒ if (npe.elementType.isDefined) { npe.elementType.get match { @@ -120,20 +103,22 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { * @param path The path element to be transformed. * @param fpe2Sci A mapping from [[FlatPathElement.element]] values to * [[StringConstancyInformation]]. Make use of this mapping if some - * StringConstancyInformation need to be used that the [[IntraproceduralInterpretationHandler]] - * cannot infer / derive. For instance, if the exact value of an expression needs - * to be determined by calling the + * StringConstancyInformation need to be used that the + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler]] + * cannot infer / derive. For instance, if the exact value of an + * expression needs to be determined by calling the * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis]] * on another instance, store this information in fpe2Sci. - * @param resetExprHandler Whether to reset the underlying [[IntraproceduralInterpretationHandler]] or not. - * When calling this function from outside, the default value should do - * fine in most of the cases. For further information, see - * [[IntraproceduralInterpretationHandler.reset]]. + * @param resetExprHandler Whether to reset the underlying + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler]] + * or not. When calling this function from outside, the default value + * should do fine in most of the cases. For further information, see + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler.reset]]. * * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed - * [[org.opalj.br.fpcf.properties.properties.StringTree]] will be returned. Note that all elements of the tree will be defined, - * i.e., if `path` contains sites that could not be processed (successfully), they will - * not occur in the tree. + * [[org.opalj.br.fpcf.properties.properties.StringTree]] will be returned. Note that + * all elements of the tree will be defined, i.e., if `path` contains sites that could + * not be processed (successfully), they will not occur in the tree. */ def pathToStringTree( path: Path, @@ -157,7 +142,7 @@ class PathTransformer(val cfg: CFG[Stmt[V], TACStmts[V]]) { } } if (resetExprHandler) { - exprHandler.reset() + interpretationHandler.reset() } tree } From 6faabc148f447d4c7e25752ecbcb0bdf2c5b5e08 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 16:21:38 +0100 Subject: [PATCH 144/316] Corrected the "extractFromPPCR" method. --- .../opalj/br/fpcf/properties/StringConstancyProperty.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index cb4b073675..fce277973e 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -60,9 +60,8 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta * Extracts a [[Result]] from the geiven `ppcr` and returns its property as an instance of this * class. */ - def extractFromPPCR(ppcr: ProperPropertyComputationResult): StringConstancyProperty = { - ppcr.asInstanceOf[Result].asInstanceOf[StringConstancyProperty] - } + def extractFromPPCR(ppcr: ProperPropertyComputationResult): StringConstancyProperty = + ppcr.asInstanceOf[Result].finalEP.p.asInstanceOf[StringConstancyProperty] /** * @return Returns the / a neutral [[StringConstancyProperty]] element, i.e., an element for From eb930e85d4b706137aada52490f25e63a18aee06 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 16:27:33 +0100 Subject: [PATCH 145/316] Continued making the analysis interprocedural. --- .../InterproceduralTestMethods.java | 41 +++++++----- .../InterproceduralStringAnalysis.scala | 56 ++++++++++++++--- .../AbstractStringInterpreter.scala | 42 +++++++++++++ ...InterproceduralInterpretationHandler.scala | 6 +- ...ralNonVirtualFunctionCallInterpreter.scala | 27 +------- ...duralNonVirtualMethodCallInterpreter.scala | 63 ++++++++++++------- ...ceduralStaticFunctionCallInterpreter.scala | 59 +++++++++++++++-- ...eduralVirtualFunctionCallInterpreter.scala | 23 ++++--- 8 files changed, 230 insertions(+), 87 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 6acd245a8b..68c17514b2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -4,7 +4,7 @@ import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; -import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.DYNAMIC; +import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.CONSTANT; /** * This file contains various tests for the InterproceduralStringAnalysis. For further information @@ -14,33 +14,46 @@ */ public class InterproceduralTestMethods { - private String someStringField = ""; - public static final String MY_CONSTANT = "mine"; - /** - * This method represents the test method which is serves as the trigger point for the - * {@link org.opalj.fpcf.LocalStringAnalysisTest} to know which string read operation to - * analyze. - * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture - * only one read operation. For how to get around this limitation, see the annotation. - * - * @param s Some string which is to be analyzed. + * {@see LocalTestMethods#analyzeString} */ public void analyzeString(String s) { } @StringDefinitionsCollection( - value = "at this point, function call cannot be handled => DYNAMIC", + value = "a case where a very simple non-virtual function call is interpreted", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" ) }) - public void fromFunctionCall() { + public void simpleNonVirtualFunctionCallTest() { String className = getStringBuilderClassName(); analyzeString(className); } + @StringDefinitionsCollection( + value = "a case where the initialization of a StringBuilder depends on > 1 non-virtual " + + "function calls and a constant", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)" + ) + }) + public void initFromNonVirtualFunctionCallTest(int i) { + String s; + if (i == 0) { + s = getRuntimeClassName(); + } else if (i == 1) { + s = getStringBuilderClassName(); + } else { + s = "ERROR"; + } + StringBuilder sb = new StringBuilder(s); + analyzeString(sb.toString()); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 03ddc14da7..09f6b86699 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -130,14 +130,13 @@ class InterproceduralStringAnalysis( } val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) - val leanPaths = paths.makeLeanPath(uvar, stmts) + state.computedLeanPath = Some(paths.makeLeanPath(uvar, stmts)) // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(leanPaths, stmts, uvar) + val dependentVars = findDependentVars(state.computedLeanPath.get, stmts, uvar) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar ⇒ val toAnalyze = (nextVar, data._2) - state.computedLeanPath = Some(leanPaths) dependentVars.foreach { case (k, v) ⇒ state.var2IndexMapping(k) = v } val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { @@ -151,12 +150,17 @@ class InterproceduralStringAnalysis( } } } else { - val interpretationHandler = InterproceduralInterpretationHandler( + val iHandler = InterproceduralInterpretationHandler( cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) ) - sci = new PathTransformer( - interpretationHandler - ).pathToStringTree(leanPaths).reduce(true) + if (computeResultsForPath(state.computedLeanPath.get, iHandler, state)) { + val interHandler = InterproceduralInterpretationHandler( + cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) + ) + sci = new PathTransformer(interHandler).pathToStringTree( + state.computedLeanPath.get, state.fpe2sci.toMap + ).reduce(true) + } } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { @@ -276,6 +280,44 @@ class InterproceduralStringAnalysis( case _ ⇒ throw new IllegalStateException("Could not process the continuation successfully.") } + /** + * This function traversed the given path, computes all string values along the path and stores + * these information in the given state. + * + * @param p The path to traverse. + * @param iHandler The handler for interpreting string related sites. + * @param state The current state of the computation. This function will extend + * [[ComputationState.fpe2sci]]. + * @return Returns `true` if all values computed for the path are final results. + */ + private def computeResultsForPath( + p: Path, + iHandler: InterproceduralInterpretationHandler, + state: ComputationState + ): Boolean = { + var hasFinalResult = true + + p.elements.foreach { + case FlatPathElement(index) ⇒ + if (!state.fpe2sci.contains(index)) { + iHandler.processDefSite(index) match { + case Result(r) ⇒ + val p = r.p.asInstanceOf[StringConstancyProperty] + state.fpe2sci(index) = p.stringConstancyInformation + case _ ⇒ hasFinalResult = false + } + } + case npe: NestedPathElement ⇒ + val subFinalResult = computeResultsForPath(Path(List(npe)), iHandler, state) + if (hasFinalResult) { + hasFinalResult = subFinalResult + } + case _ ⇒ + } + + hasFinalResult + } + /** * Helper / accumulator function for finding dependees. For how dependees are detected, see * findDependentVars. Returns a list of pairs of DUVar and the index of the diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 2c5794a6b6..ed5905feb4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -1,11 +1,20 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import scala.collection.mutable.ListBuffer + import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore import org.opalj.br.cfg.CFG +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethods import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.properties.TACAI /** * @param cfg The control flow graph that underlies the instruction to interpret. @@ -26,6 +35,39 @@ abstract class AbstractStringInterpreter( type T <: Any + /** + * Either returns the TAC for the given method or otherwise registers dependees. + * + * @param ps The property store to use. + * @param m The method to get the TAC for. + * @param s The computation state whose dependees might be extended in case the TAC is not + * immediately ready. + * @return Returns `Some(tac)` if the TAC is already available or `None` otherwise. + */ + protected def getTACAI( + ps: PropertyStore, + m: Method, + s: ComputationState + ): Option[TACode[TACMethodParameter, V]] = { + val tacai = ps(m, TACAI.key) + if (tacai.hasUBP) { + tacai.ub.tac + } else { + if (!s.dependees.contains(m)) { + s.dependees(m) = ListBuffer() + } + s.dependees(m).append(tacai) + None + } + } + + /** + * Takes `declaredMethods` as well as a method `name`, extracts the method with the given `name` + * from `declaredMethods` and returns this one as a [[Method]]. + */ + protected def getDeclaredMethod(declaredMethods: DeclaredMethods, name: String): Method = + declaredMethods.declaredMethods.find(_.name == name).get.definedMethod + /** * * @param instr The instruction that is to be interpreted. It is the responsibility of diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 43997268c9..e7cc95e670 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -83,7 +83,7 @@ class InterproceduralInterpretationHandler( ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( - cfg, this, callees + cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) case Assignment(_, _, expr: BinaryExpr[V]) ⇒ new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) @@ -99,7 +99,7 @@ class InterproceduralInterpretationHandler( ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( - cfg, this, callees + cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) case vmc: VirtualMethodCall[V] ⇒ new InterproceduralVirtualMethodCallInterpreter( @@ -107,7 +107,7 @@ class InterproceduralInterpretationHandler( ).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] ⇒ new InterproceduralNonVirtualMethodCallInterpreter( - cfg, this, callees + cfg, this, ps, state, declaredMethods, c ).interpret(nvmc, defSite) case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala index 94bec79303..9a33f37b1b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -9,19 +9,14 @@ import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeEOptionP import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.Method import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.ReturnValue -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState /** @@ -43,21 +38,6 @@ class InterproceduralNonVirtualFunctionCallInterpreter( override type T = NonVirtualFunctionCall[V] - def getTACAI( - m: Method, dependees: ListBuffer[SomeEOptionP] - ): Option[TACode[TACMethodParameter, V]] = { - val tacai = ps(m, TACAI.key) - if (tacai.hasUBP) { - tacai.ub.tac - } else { - if (!state.dependees.contains(m)) { - state.dependees(m) = ListBuffer() - } - state.dependees(m).append(tacai) - None - } - } - /** * Currently, [[NonVirtualFunctionCall]]s are not supported. Thus, this function always returns * a list with a single element consisting of @@ -70,9 +50,8 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val dependees = ListBuffer[SomeEOptionP]() - val m = declaredMethods.declaredMethods.find(_.name == instr.name).get.definedMethod - val tac = getTACAI(m, dependees) + val m = getDeclaredMethod(declaredMethods, instr.name) + val tac = getTACAI(ps, m, state) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get @@ -103,7 +82,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( m, StringConstancyProperty.lowerBound, StringConstancyProperty.upperBound, - dependees, + state.dependees.values.flatten, c ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala index 968e6bbe8a..a979fe0f45 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -1,17 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation -import scala.collection.mutable.ListBuffer - +import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result +import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V /** @@ -24,9 +25,14 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V * @author Patrick Mell */ class InterproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees + cfg: CFG[Stmt[V], TACStmts[V]], + // TODO: Do not let an instance of InterproceduralInterpretationHandler handler pass here + // but let it be instantiated in this class + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: ComputationState, + declaredMethods: DeclaredMethods, + c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] @@ -49,11 +55,11 @@ class InterproceduralNonVirtualMethodCallInterpreter( override def interpret( instr: NonVirtualMethodCall[V], defSite: Int ): ProperPropertyComputationResult = { - val prop = instr.name match { - case "" ⇒ interpretInit(instr) - case _ ⇒ StringConstancyProperty.getNeutralElement + val e: Integer = defSite + instr.name match { + case "" ⇒ interpretInit(instr, e) + case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } - Result(instr, prop) } /** @@ -63,20 +69,35 @@ class InterproceduralNonVirtualMethodCallInterpreter( * [[StringBuffer]] and [[StringBuilder]], have only constructors with <= 1 arguments and only * these are currently interpreted). */ - private def interpretInit(init: NonVirtualMethodCall[V]): StringConstancyProperty = { + private def interpretInit( + init: NonVirtualMethodCall[V], defSite: Integer + ): ProperPropertyComputationResult = { init.params.size match { - case 0 ⇒ StringConstancyProperty.getNeutralElement + case 0 ⇒ Result(defSite, StringConstancyProperty.getNeutralElement) case _ ⇒ - val scis = ListBuffer[StringConstancyInformation]() - init.params.head.asVar.definedBy.foreach { ds ⇒ - val result = exprHandler.processDefSite(ds) - val prop = result.asInstanceOf[Result].finalEP.p - scis.append( - prop.asInstanceOf[StringConstancyProperty].stringConstancyInformation - ) + val results = init.params.head.asVar.definedBy.map { ds: Int ⇒ + (ds, exprHandler.processDefSite(ds)) + } + if (results.forall(_._2.isInstanceOf[Result])) { + // Final result is available + val scis = results.map(r ⇒ + StringConstancyProperty.extractFromPPCR(r._2).stringConstancyInformation) + val reduced = StringConstancyInformation.reduceMultiple(scis.toList) + Result(defSite, StringConstancyProperty(reduced)) + } else { + // Some intermediate results => register necessary information from final + // results and return an intermediate result + val returnIR = results.find(r ⇒ !r._2.isInstanceOf[Result]).get._2 + results.foreach { + case (ds, Result(r)) ⇒ + val p = r.p.asInstanceOf[StringConstancyProperty] + state.fpe2sci(ds) = p.stringConstancyInformation + case _ ⇒ + } + // TODO: is it enough to return only one (the first) IntermediateResult in case + // there are more? (The others were registered already, anyway.) + returnIR } - val reduced = StringConstancyInformation.reduceMultiple(scis.toList) - StringConstancyProperty(reduced) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala index a57b59ae86..489bacce89 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -1,15 +1,23 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result +import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.ReturnValue /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -22,9 +30,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V * @author Patrick Mell */ class InterproceduralStaticFunctionCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: ComputationState, + declaredMethods: DeclaredMethods, + c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] @@ -39,7 +50,43 @@ class InterproceduralStaticFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + val m = getDeclaredMethod(declaredMethods, instr.name) + val tac = getTACAI(ps, m, state) + if (tac.isDefined) { + // TAC available => Get return UVar and start the string analysis + val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get + val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + val entity = (uvar, m) + + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case FinalEP(e, p) ⇒ + Result(e, p) + case _ ⇒ + if (!state.dependees.contains(m)) { + state.dependees(m) = ListBuffer() + } + state.dependees(m).append(eps) + state.var2IndexMapping(uvar) = defSite + InterimResult( + entity, + StringConstancyProperty.lowerBound, + StringConstancyProperty.upperBound, + List(), + c + ) + } + } else { + // No TAC => Register dependee and continue + InterimResult( + m, + StringConstancyProperty.lowerBound, + StringConstancyProperty.upperBound, + state.dependees.values.flatten, + c + ) + } + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala index 5b15dac1ad..30f189c105 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala @@ -61,19 +61,19 @@ class InterproceduralVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val property = instr.name match { + val e: Integer = defSite + instr.name match { case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) case "replace" ⇒ interpretReplaceCall(instr) case _ ⇒ instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ - StringConstancyProperty.lowerBound - case _ ⇒ StringConstancyProperty.getNeutralElement + Result(e, StringConstancyProperty.lowerBound) + case _ ⇒ + Result(e, StringConstancyProperty.getNeutralElement) } } - - Result(instr, property) } /** @@ -83,7 +83,7 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V] - ): StringConstancyProperty = { + ): ProperPropertyComputationResult = { val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation @@ -111,7 +111,7 @@ class InterproceduralVirtualFunctionCallInterpreter( ) } - StringConstancyProperty(sci) + Result(appendCall, StringConstancyProperty(sci)) } /** @@ -198,10 +198,8 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def interpretToStringCall( call: VirtualFunctionCall[V] - ): StringConstancyProperty = { - val result = exprHandler.processDefSite(call.receiver.asVar.definedBy.head) - result.asInstanceOf[StringConstancyProperty] - } + ): ProperPropertyComputationResult = + exprHandler.processDefSite(call.receiver.asVar.definedBy.head) /** * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. @@ -210,6 +208,7 @@ class InterproceduralVirtualFunctionCallInterpreter( */ private def interpretReplaceCall( instr: VirtualFunctionCall[V] - ): StringConstancyProperty = InterpretationHandler.getStringConstancyPropertyForReplace + ): ProperPropertyComputationResult = + Result(instr, InterpretationHandler.getStringConstancyPropertyForReplace) } From 1fb06fae78d7feae25f390b44f6e007ece12f731 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:13:36 +0100 Subject: [PATCH 146/316] Fixed a little but in the local string analysis which lead to the fact that transitive dependencies were not resolved correctly. --- .../string_analysis/LocalTestMethods.java | 4 ---- .../string_analysis/LocalStringAnalysis.scala | 18 +++++++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index a48585dfad..8418e6ac84 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -754,9 +754,6 @@ public void directAppendConcatsWith2ndStringBuilder() { value = "checks if the case, where the value of a StringBuilder depends on the " + "complex construction of a second StringBuilder is determined correctly.", stringDefinitions = { - @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(Object|Runtime)" - ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "java.lang.(Object|Runtime)" ) @@ -772,7 +769,6 @@ public void secondStringBuilderRead(String className) { sb1.append(sbRun.toString()); } - analyzeString(sb1.toString()); StringBuilder sb2 = new StringBuilder("java.lang."); sb2.append(sb1.toString()); analyzeString(sb2.toString()); diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala index 3df2aebf13..b0d0df0fd8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala @@ -27,7 +27,6 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder @@ -89,7 +88,7 @@ class LocalStringAnalysis( val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) // If not empty, this very routine can only produce an intermediate result - val dependees = mutable.Map[Entity, EOptionP[Entity, Property]]() + val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() // state will be set to a non-null value if this analysis needs to call other analyses / // itself; only in the case it calls itself, will state be used, thus, it is valid to // initialize it with null @@ -116,9 +115,12 @@ class LocalStringAnalysis( val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ - return processFinalP(data, dependees.values, state, ep.e, p) + return processFinalP(data, dependees.values.flatten, state, ep.e, p) case _ ⇒ - dependees.put(toAnalyze, ep) + if (!dependees.contains(data)) { + dependees(data) = ListBuffer() + } + dependees(data).append(ep) } } } else { @@ -140,11 +142,11 @@ class LocalStringAnalysis( if (dependees.nonEmpty) { InterimResult( - data, + data._1, StringConstancyProperty.upperBound, StringConstancyProperty.lowerBound, - dependees.values, - continuation(data, dependees.values, state) + dependees.values.flatten, + continuation(data, dependees.values.flatten, state) ) } else { Result(data, StringConstancyProperty(sci)) @@ -296,6 +298,8 @@ class LocalStringAnalysis( sealed trait LocalStringAnalysisScheduler extends FPCFAnalysisScheduler { + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) + final override def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), PropertyBounds.ub(Callees), From f11b7998608a1a3bdd381d9c3b43cbe3e3abd8fc Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:21:48 +0100 Subject: [PATCH 147/316] Renamed "LocalStringAnalysis" to "IntraproceduralStringAnalysis". --- .../info/StringAnalysisReflectiveCalls.scala | 4 +-- .../string_analysis/LocalTestMethods.java | 2 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 26 +++++++++---------- .../InterproceduralStringAnalysis.scala | 2 +- ...la => IntraproceduralStringAnalysis.scala} | 20 +++++++------- .../preprocessing/PathTransformer.scala | 2 +- .../string_analysis/string_analysis.scala | 4 +-- 7 files changed, 29 insertions(+), 31 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/{LocalStringAnalysis.scala => IntraproceduralStringAnalysis.scala} (95%) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index f22dd4f793..5c437e662f 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -30,7 +30,7 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall -import org.opalj.tac.fpcf.analyses.string_analysis.LazyLocalStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.P import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -196,7 +196,7 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { val t0 = System.currentTimeMillis() implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) - project.get(FPCFAnalysesManagerKey).runAll(LazyLocalStringAnalysis) + project.get(FPCFAnalysesManagerKey).runAll(LazyIntraproceduralStringAnalysis) val tacProvider = project.get(SimpleTACAIKey) // Stores the obtained results for each supported reflective operation diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index 8418e6ac84..dae4d8e8bb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -55,7 +55,7 @@ public class LocalTestMethods { /** * This method represents the test method which is serves as the trigger point for the - * {@link org.opalj.fpcf.LocalStringAnalysisTest} to know which string read operation to + * {@link org.opalj.fpcf.IntraproceduralStringAnalysisTest} to know which string read operation to * analyze. * Note that the {@link StringDefinitions} annotation is designed in a way to be able to capture * only one read operation. For how to get around this limitation, see the annotation. diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index be8b534e07..1e296e769b 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -30,9 +30,8 @@ import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.LazyLocalStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis @@ -41,6 +40,7 @@ import org.opalj.tac.fpcf.analyses.cg.TriggeredThreadRelatedCallsAnalysis import org.opalj.tac.fpcf.analyses.TACAITransformer import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -152,27 +152,27 @@ sealed class StringAnalysisTestRunner( } /** - * Tests whether the [[LocalStringAnalysis]] works correctly with respect to some well-defined + * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some well-defined * tests. * * @author Patrick Mell */ -class LocalStringAnalysisTest extends PropertiesTest { +class IntraproceduralStringAnalysisTest extends PropertiesTest { describe("the org.opalj.fpcf.LocalStringAnalysis is started") { val runner = new StringAnalysisTestRunner( - LocalStringAnalysisTest.fqTestMethodsClass, - LocalStringAnalysisTest.nameTestMethod, - LocalStringAnalysisTest.filesToLoad + IntraproceduralStringAnalysisTest.fqTestMethodsClass, + IntraproceduralStringAnalysisTest.nameTestMethod, + IntraproceduralStringAnalysisTest.filesToLoad ) val p = Project(runner.getRelevantProjectFiles, Array[File]()) val manager = p.get(FPCFAnalysesManagerKey) - val (ps, _) = manager.runAll(LazyLocalStringAnalysis) - val testContext = TestContext(p, ps, List(new LocalStringAnalysis(p))) + val (ps, _) = manager.runAll(LazyIntraproceduralStringAnalysis) + val testContext = TestContext(p, ps, List(new IntraproceduralStringAnalysis(p))) - LazyLocalStringAnalysis.init(p, ps) - LazyLocalStringAnalysis.schedule(ps, null) + LazyIntraproceduralStringAnalysis.init(p, ps) + LazyIntraproceduralStringAnalysis.schedule(ps, null) val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) @@ -183,7 +183,7 @@ class LocalStringAnalysisTest extends PropertiesTest { } -object LocalStringAnalysisTest { +object IntraproceduralStringAnalysisTest { val fqTestMethodsClass = "org.opalj.fpcf.fixtures.string_analysis.LocalTestMethods" // The name of the method from which to extract DUVars to analyze @@ -196,7 +196,7 @@ object LocalStringAnalysisTest { } /** - * Tests whether the [[LocalStringAnalysis]] works correctly with respect to some well-defined + * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some well-defined * tests. * * @author Patrick Mell diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 09f6b86699..0e27573b65 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -68,7 +68,7 @@ case class ComputationState( * InterproceduralStringAnalysis processes a read operation of a string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. * - * In comparison to [[LocalStringAnalysis]], this version tries to resolve method calls that are + * In comparison to [[IntraproceduralStringAnalysis]], this version tries to resolve method calls that are * involved in a string construction as far as possible. * * @author Patrick Mell diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index b0d0df0fd8..a8bf530687 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/LocalStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -39,19 +39,16 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinde import org.opalj.tac.fpcf.properties.TACAI /** - * LocalStringAnalysis processes a read operation of a local string variable at a program + * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. * - * "Local" as this analysis takes into account only the enclosing function as a context, i.e., it + * This analysis takes into account only the enclosing function as a context, i.e., it * intraprocedural. Values coming from other functions are regarded as dynamic values even if the * function returns a constant string value. * - * The StringConstancyProperty might contain more than one possible string, e.g., if the source of - * the value is an array. - * * @author Patrick Mell */ -class LocalStringAnalysis( +class IntraproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { @@ -296,7 +293,7 @@ class LocalStringAnalysis( } -sealed trait LocalStringAnalysisScheduler extends FPCFAnalysisScheduler { +sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) @@ -306,9 +303,9 @@ sealed trait LocalStringAnalysisScheduler extends FPCFAnalysisScheduler { PropertyBounds.lub(StringConstancyProperty) ) - final override type InitializationData = LocalStringAnalysis + final override type InitializationData = IntraproceduralStringAnalysis final override def init(p: SomeProject, ps: PropertyStore): InitializationData = { - new LocalStringAnalysis(p) + new IntraproceduralStringAnalysis(p) } override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} @@ -326,12 +323,13 @@ sealed trait LocalStringAnalysisScheduler extends FPCFAnalysisScheduler { /** * Executor for the lazy analysis. */ -object LazyLocalStringAnalysis extends LocalStringAnalysisScheduler with FPCFLazyAnalysisScheduler { +object LazyIntraproceduralStringAnalysis + extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData ): FPCFAnalysis = { - val analysis = new LocalStringAnalysis(p) + val analysis = new IntraproceduralStringAnalysis(p) ps.registerLazyPropertyComputation(StringConstancyProperty.key, analysis.analyze) analysis } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 6cce173c88..5c2cdd5c37 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -107,7 +107,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler]] * cannot infer / derive. For instance, if the exact value of an * expression needs to be determined by calling the - * [[org.opalj.tac.fpcf.analyses.string_analysis.LocalStringAnalysis]] + * [[org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis]] * on another instance, store this information in fpe2Sci. * @param resetExprHandler Whether to reset the underlying * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler]] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index aea1caf9e4..f32bbe5571 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -11,14 +11,14 @@ import org.opalj.tac.DUVar package object string_analysis { /** - * The type of entities the [[LocalStringAnalysis]] processes. + * The type of entities the [[IntraproceduralStringAnalysis]] processes. * * @note The analysis requires further context information, see [[P]]. */ type V = DUVar[ValueInformation] /** - * [[LocalStringAnalysis]] processes a local variable within the context of a + * [[IntraproceduralStringAnalysis]] processes a local variable within the context of a * particular context, i.e., the method in which it is used. */ type P = (V, Method) From 814ef3a53bfcda355154d2bec8f11929e659acf8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:28:40 +0100 Subject: [PATCH 148/316] Had to add the "derivedProperty". --- .../string_analysis/InterproceduralStringAnalysis.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 0e27573b65..054e5a1d1a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -28,7 +28,6 @@ import org.opalj.br.DeclaredMethod import org.opalj.br.analyses.DeclaredMethods import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis.derivedProperty import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.properties.TACAI @@ -407,6 +406,8 @@ class InterproceduralStringAnalysis( sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { + final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) + final override def uses: Set[PropertyBounds] = Set( PropertyBounds.ub(TACAI), PropertyBounds.ub(Callees), From 36b1b17199c5c6c129ecb300bca4b7db9539cfdf Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 31 Jan 2019 19:36:30 +0100 Subject: [PATCH 149/316] Renamed the upper and lower bound of the StringConstancyProperty to "ub" and "lb". --- .../properties/StringConstancyProperty.scala | 6 +++--- .../InterproceduralStringAnalysis.scala | 18 +++++++++--------- .../IntraproceduralStringAnalysis.scala | 14 +++++++------- .../InterproceduralArrayInterpreter.scala | 2 +- .../InterproceduralFieldInterpreter.scala | 2 +- .../InterproceduralGetStaticInterpreter.scala | 4 ++-- .../InterproceduralInterpretationHandler.scala | 2 +- ...uralNonVirtualFunctionCallInterpreter.scala | 8 ++++---- ...oceduralStaticFunctionCallInterpreter.scala | 8 ++++---- ...ceduralVirtualFunctionCallInterpreter.scala | 4 ++-- .../IntraproceduralArrayInterpreter.scala | 2 +- .../IntraproceduralFieldInterpreter.scala | 4 ++-- .../IntraproceduralGetStaticInterpreter.scala | 4 ++-- .../IntraproceduralInterpretationHandler.scala | 2 +- ...uralNonVirtualFunctionCallInterpreter.scala | 4 ++-- ...oceduralStaticFunctionCallInterpreter.scala | 4 ++-- ...ceduralVirtualFunctionCallInterpreter.scala | 4 ++-- 17 files changed, 46 insertions(+), 46 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index fce277973e..87bc7b80bb 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -47,7 +47,7 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta PropertyKeyName, (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases - lowerBound + lb }, ) } @@ -73,7 +73,7 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta /** * @return Returns the upper bound from a lattice-point of view. */ - def upperBound: StringConstancyProperty = + def ub: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND )) @@ -81,7 +81,7 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta /** * @return Returns the lower bound from a lattice-point of view. */ - def lowerBound: StringConstancyProperty = + def lb: StringConstancyProperty = StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.DYNAMIC, StringConstancyType.APPEND, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 054e5a1d1a..9ac0b3fd1f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -93,8 +93,8 @@ class InterproceduralStringAnalysis( val dependees = Iterable(calleesEOptP) InterimResult( calleesEOptP, - StringConstancyProperty.lowerBound, - StringConstancyProperty.upperBound, + StringConstancyProperty.lb, + StringConstancyProperty.ub, dependees, calleesContinuation(calleesEOptP, dependees, data) ) @@ -105,7 +105,7 @@ class InterproceduralStringAnalysis( data: P, callees: Callees ): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) - var sci = StringConstancyProperty.lowerBound.stringConstancyInformation + var sci = StringConstancyProperty.lb.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) val cfg = tacProvider(data._2).cfg val stmts = cfg.code.instructions @@ -116,7 +116,7 @@ class InterproceduralStringAnalysis( // Function parameters are currently regarded as dynamic value; the following if finds read // operations of strings (not String{Builder, Buffer}s, they will be handles further down if (defSites.head < 0) { - return Result(data, StringConstancyProperty.lowerBound) + return Result(data, StringConstancyProperty.lb) } val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) @@ -125,7 +125,7 @@ class InterproceduralStringAnalysis( val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated if (initDefSites.isEmpty) { - return Result(data, StringConstancyProperty.lowerBound) + return Result(data, StringConstancyProperty.lb) } val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) @@ -190,8 +190,8 @@ class InterproceduralStringAnalysis( if (state.dependees.nonEmpty) { InterimResult( data, - StringConstancyProperty.upperBound, - StringConstancyProperty.lowerBound, + StringConstancyProperty.ub, + StringConstancyProperty.lb, state.dependees.values.flatten, continuation(data, callees, state.dependees.values.flatten, state) ) @@ -249,8 +249,8 @@ class InterproceduralStringAnalysis( } else { InterimResult( data, - StringConstancyProperty.upperBound, - StringConstancyProperty.lowerBound, + StringConstancyProperty.ub, + StringConstancyProperty.lb, remDependees, continuation(data, callees, remDependees, state) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index a8bf530687..804a743d76 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -70,7 +70,7 @@ class IntraproceduralStringAnalysis( def analyze(data: P): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) - var sci = StringConstancyProperty.lowerBound.stringConstancyInformation + var sci = StringConstancyProperty.lb.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) val cfg = tacProvider(data._2).cfg val stmts = cfg.code.instructions @@ -80,7 +80,7 @@ class IntraproceduralStringAnalysis( // Function parameters are currently regarded as dynamic value; the following if finds read // operations of strings (not String{Builder, Buffer}s, they will be handles further down if (defSites.head < 0) { - return Result(data, StringConstancyProperty.lowerBound) + return Result(data, StringConstancyProperty.lb) } val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) @@ -96,7 +96,7 @@ class IntraproceduralStringAnalysis( val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated if (initDefSites.isEmpty) { - return Result(data, StringConstancyProperty.lowerBound) + return Result(data, StringConstancyProperty.lb) } val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) @@ -140,8 +140,8 @@ class IntraproceduralStringAnalysis( if (dependees.nonEmpty) { InterimResult( data._1, - StringConstancyProperty.upperBound, - StringConstancyProperty.lowerBound, + StringConstancyProperty.ub, + StringConstancyProperty.lb, dependees.values.flatten, continuation(data, dependees.values.flatten, state) ) @@ -177,8 +177,8 @@ class IntraproceduralStringAnalysis( } else { InterimResult( data, - StringConstancyProperty.upperBound, - StringConstancyProperty.lowerBound, + StringConstancyProperty.ub, + StringConstancyProperty.lb, remDependees, continuation(data, remDependees, state) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala index 75682c61c8..dd2a3eaef5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala @@ -71,7 +71,7 @@ class InterproceduralArrayInterpreter( // In case it refers to a method parameter, add a dynamic string property if (defSites.exists(_ < 0)) { - children.append(StringConstancyProperty.lowerBound.stringConstancyInformation) + children.append(StringConstancyProperty.lb.stringConstancyInformation) } Result(instr, StringConstancyProperty( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala index 2693c03119..ddd528baa3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala @@ -41,6 +41,6 @@ class InterproceduralFieldInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + Result(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala index 9d7d929bf6..3a5c520a64 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala @@ -27,13 +27,13 @@ class InterproceduralGetStaticInterpreter( /** * Currently, this type is not interpreted. Thus, this function always returns a result - * containing [[StringConstancyProperty.lowerBound]]. + * containing [[StringConstancyProperty.lb]]. * * @note For this implementation, `defSite` plays a role! * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + Result(instr, StringConstancyProperty.lb) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index e7cc95e670..03ff7174be 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -59,7 +59,7 @@ class InterproceduralInterpretationHandler( val e: Integer = defSite.toInt // Function parameters are not evaluated but regarded as unknown if (defSite < 0) { - return Result(e, StringConstancyProperty.lowerBound) + return Result(e, StringConstancyProperty.lb) } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala index 9a33f37b1b..2312074ac0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -70,8 +70,8 @@ class InterproceduralNonVirtualFunctionCallInterpreter( state.var2IndexMapping(uvar) = defSite InterimResult( entity, - StringConstancyProperty.lowerBound, - StringConstancyProperty.upperBound, + StringConstancyProperty.lb, + StringConstancyProperty.ub, List(), c ) @@ -80,8 +80,8 @@ class InterproceduralNonVirtualFunctionCallInterpreter( // No TAC => Register dependee and continue InterimResult( m, - StringConstancyProperty.lowerBound, - StringConstancyProperty.upperBound, + StringConstancyProperty.lb, + StringConstancyProperty.ub, state.dependees.values.flatten, c ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala index 489bacce89..658f88d99f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -71,8 +71,8 @@ class InterproceduralStaticFunctionCallInterpreter( state.var2IndexMapping(uvar) = defSite InterimResult( entity, - StringConstancyProperty.lowerBound, - StringConstancyProperty.upperBound, + StringConstancyProperty.lb, + StringConstancyProperty.ub, List(), c ) @@ -81,8 +81,8 @@ class InterproceduralStaticFunctionCallInterpreter( // No TAC => Register dependee and continue InterimResult( m, - StringConstancyProperty.lowerBound, - StringConstancyProperty.upperBound, + StringConstancyProperty.lb, + StringConstancyProperty.ub, state.dependees.values.flatten, c ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala index 30f189c105..2f666b93e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala @@ -49,7 +49,7 @@ class InterproceduralVirtualFunctionCallInterpreter( * [[InterproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. * *

  • - * Apart from these supported methods, a list with [[StringConstancyProperty.lowerBound]] + * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] * will be returned in case the passed method returns a [[java.lang.String]]. *
  • * @@ -69,7 +69,7 @@ class InterproceduralVirtualFunctionCallInterpreter( case _ ⇒ instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ - Result(e, StringConstancyProperty.lowerBound) + Result(e, StringConstancyProperty.lb) case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala index e533e06494..9d57cea345 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala @@ -70,7 +70,7 @@ class IntraproceduralArrayInterpreter( // In case it refers to a method parameter, add a dynamic string property if (defSites.exists(_ < 0)) { - children.append(StringConstancyProperty.lowerBound.stringConstancyInformation) + children.append(StringConstancyProperty.lb.stringConstancyInformation) } Result(instr, StringConstancyProperty( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala index 4a6f262a0c..f23c717c8e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala @@ -29,13 +29,13 @@ class IntraproceduralFieldInterpreter( /** * Fields are not suppoerted by this implementation. Thus, this function always returns a result - * containing [[StringConstancyProperty.lowerBound]]. + * containing [[StringConstancyProperty.lb]]. * * @note For this implementation, `defSite` does not play a role. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + Result(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala index 627a611481..3fe64272ef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala @@ -29,13 +29,13 @@ class IntraproceduralGetStaticInterpreter( /** * Currently, this type is not interpreted. Thus, this function always returns a result - * containing [[StringConstancyProperty.lowerBound]]. + * containing [[StringConstancyProperty.lb]]. * * @note For this implementation, `defSite` does not play a role. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + Result(instr, StringConstancyProperty.lb) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala index 14b57a2aa1..f1531b2320 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -49,7 +49,7 @@ class IntraproceduralInterpretationHandler( val e: Integer = defSite.toInt // Function parameters are not evaluated but regarded as unknown if (defSite < 0) { - return Result(e, StringConstancyProperty.lowerBound) + return Result(e, StringConstancyProperty.lb) } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala index 45a2cf9a35..c7c9ea0a15 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala @@ -26,13 +26,13 @@ class IntraproceduralNonVirtualFunctionCallInterpreter( override type T = NonVirtualFunctionCall[V] /** - * This function always returns a result that contains [[StringConstancyProperty.lowerBound]]. + * This function always returns a result that contains [[StringConstancyProperty.lb]]. * * @note For this implementation, `defSite` does not play a role. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + Result(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala index 346a241c73..4f95bab678 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala @@ -28,13 +28,13 @@ class IntraproceduralStaticFunctionCallInterpreter( override type T = StaticFunctionCall[V] /** - * This function always returns a result containing [[StringConstancyProperty.lowerBound]]. + * This function always returns a result containing [[StringConstancyProperty.lb]]. * * @note For this implementation, `defSite` does not play a role. * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lowerBound) + Result(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala index 09ab919d7b..649efb179e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -47,7 +47,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * [[IntraproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. * *
  • - * Apart from these supported methods, a list with [[StringConstancyProperty.lowerBound]] + * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] * will be returned in case the passed method returns a [[java.lang.String]]. *
  • * @@ -67,7 +67,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( case _ ⇒ instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ - StringConstancyProperty.lowerBound + StringConstancyProperty.lb case _ ⇒ StringConstancyProperty.getNeutralElement } } From e97a884de21be645d1ee2869ee37441626d82c5a Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Feb 2019 08:35:20 +0100 Subject: [PATCH 150/316] Started added support for parameters (context-sensitive). --- .../InterproceduralTestMethods.java | 21 +++++++++++ .../InterproceduralStringAnalysis.scala | 37 +++++++++++++++---- .../InterpretationHandler.scala | 10 ++++- .../InterproceduralArrayInterpreter.scala | 4 +- ...InterproceduralInterpretationHandler.scala | 16 +++++--- ...duralNonVirtualMethodCallInterpreter.scala | 2 +- ...ceduralStaticFunctionCallInterpreter.scala | 10 +++++ ...eduralVirtualFunctionCallInterpreter.scala | 12 +++--- .../IntraproceduralArrayInterpreter.scala | 4 +- ...IntraproceduralInterpretationHandler.scala | 5 ++- 10 files changed, 96 insertions(+), 25 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 68c17514b2..b3bcf3cf06 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -14,6 +14,8 @@ */ public class InterproceduralTestMethods { + public static final String JAVA_LANG = "java.lang"; + /** * {@see LocalTestMethods#analyzeString} */ @@ -54,6 +56,18 @@ public void initFromNonVirtualFunctionCallTest(int i) { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "a case where a static method with a string parameter is called", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "java.lang.Integer" + ) + }) + public void fromStaticMethodWithParam() { + analyzeString(InterproceduralTestMethods.getFQClassName(JAVA_LANG, "Integer")); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } @@ -66,4 +80,11 @@ private String getSimpleStringBuilderClassName() { return "StringBuilder"; } + /** + * Returns "[packageName].[className]". + */ + public static String getFQClassName(String packageName, String className) { + return packageName + "." + className; + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 9ac0b3fd1f..4dbfe10bb7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -61,14 +61,16 @@ case class ComputationState( val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() // A mapping from values of FlatPathElements to StringConstancyInformation val fpe2sci: mutable.Map[Int, StringConstancyInformation] = mutable.Map() + + var params: List[StringConstancyInformation] = List() } /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. * - * In comparison to [[IntraproceduralStringAnalysis]], this version tries to resolve method calls that are - * involved in a string construction as far as possible. + * In comparison to [[IntraproceduralStringAnalysis]], this version tries to resolve method calls + * that are involved in a string construction as far as possible. * * @author Patrick Mell */ @@ -110,6 +112,7 @@ class InterproceduralStringAnalysis( val cfg = tacProvider(data._2).cfg val stmts = cfg.code.instructions state = ComputationState(None, cfg, Some(callees)) + state.params = InterproceduralStringAnalysis.getParams(data) val uvar = data._1 val defSites = uvar.definedBy.toArray.sorted @@ -153,10 +156,7 @@ class InterproceduralStringAnalysis( cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) ) if (computeResultsForPath(state.computedLeanPath.get, iHandler, state)) { - val interHandler = InterproceduralInterpretationHandler( - cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) - ) - sci = new PathTransformer(interHandler).pathToStringTree( + sci = new PathTransformer(iHandler).pathToStringTree( state.computedLeanPath.get, state.fpe2sci.toMap ).reduce(true) } @@ -167,7 +167,7 @@ class InterproceduralStringAnalysis( cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) ) val results = uvar.definedBy.toArray.sorted.map { ds ⇒ - (ds, interHandler.processDefSite(ds)) + (ds, interHandler.processDefSite(ds, state.params)) } val interimResults = results.filter(!_._2.isInstanceOf[Result]).map { r ⇒ (r._1, r._2.asInstanceOf[InterimResult[StringConstancyProperty]]) @@ -299,7 +299,7 @@ class InterproceduralStringAnalysis( p.elements.foreach { case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { - iHandler.processDefSite(index) match { + iHandler.processDefSite(index, state.params) match { case Result(r) ⇒ val p = r.p.asInstanceOf[StringConstancyProperty] state.fpe2sci(index) = p.stringConstancyInformation @@ -404,6 +404,27 @@ class InterproceduralStringAnalysis( } +object InterproceduralStringAnalysis { + + private val paramInfos = mutable.Map[Entity, List[StringConstancyInformation]]() + + def registerParams(e: Entity, scis: List[StringConstancyInformation]): Unit = { + if (!paramInfos.contains(e)) { + paramInfos(e) = List(scis: _*) + } + // Per entity and method, a StringConstancyInformation list sshoud be present only once, + // thus no else branch + } + + def getParams(e: Entity): List[StringConstancyInformation] = + if (paramInfos.contains(e)) { + paramInfos(e) + } else { + List() + } + +} + sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { final def derivedProperty: PropertyBounds = PropertyBounds.lub(StringConstancyProperty) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index eda3ecb2d7..27784809cb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -37,6 +37,11 @@ abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { * actually exists, and (3) can be processed by one of the subclasses of * [[AbstractStringInterpreter]] (in case (3) is violated, an * [[IllegalArgumentException]] will be thrown. + * @param params For a (precise) interpretation, (method / function) parameter values might be + * necessary. They can be leveraged using this value. The implementing classes + * should make sure that (1) they handle the case when no parameters are given + * and (2)they have a proper mapping from the definition sites within used methods + * to the indices in `params` (as the definition sites of parameters are < 0). * @return Returns the result of the interpretation. Note that depending on the concrete * interpreter either a final or an intermediate result can be returned! * In case the rules listed above or the ones of the different concrete interpreters are @@ -45,7 +50,10 @@ abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[org.opalj.br.fpcf.properties.StringConstancyProperty.isTheNeutralElement]]). * The entity of the result will be the given `defSite`. */ - def processDefSite(defSite: Int): ProperPropertyComputationResult + def processDefSite( + defSite: Int, + params: List[StringConstancyInformation] = List() + ): ProperPropertyComputationResult /** * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala index dd2a3eaef5..1fc6f5acc4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala @@ -51,7 +51,7 @@ class InterproceduralArrayInterpreter( stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - children.appendAll(sortedDefs.map { exprHandler.processDefSite }.map { + children.appendAll(sortedDefs.map { exprHandler.processDefSite(_) }.map { _.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) } @@ -63,7 +63,7 @@ class InterproceduralArrayInterpreter( } } foreach { f: Int ⇒ val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - children.appendAll(defs.toArray.sorted.map { exprHandler.processDefSite }.map { + children.appendAll(defs.toArray.sorted.map { exprHandler.processDefSite(_) }.map { _.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 03ff7174be..59d9704dd4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -8,6 +8,7 @@ import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -53,13 +54,18 @@ class InterproceduralInterpretationHandler( * * @inheritdoc */ - override def processDefSite(defSite: Int): ProperPropertyComputationResult = { + override def processDefSite( + defSite: Int, params: List[StringConstancyInformation] = List() + ): ProperPropertyComputationResult = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt - // Function parameters are not evaluated but regarded as unknown - if (defSite < 0) { + // Function parameters are not evaluated when none are present + if (defSite < 0 && params.isEmpty) { return Result(e, StringConstancyProperty.lb) + } else if (defSite < 0) { + val paramPos = Math.abs(defSite + 2) + return Result(e, StringConstancyProperty(params(paramPos))) } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } @@ -79,7 +85,7 @@ class InterproceduralInterpretationHandler( new NewInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ new InterproceduralVirtualFunctionCallInterpreter( - cfg, this, callees + cfg, this, callees, params ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( @@ -95,7 +101,7 @@ class InterproceduralInterpretationHandler( new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ new InterproceduralVirtualFunctionCallInterpreter( - cfg, this, callees + cfg, this, callees, params ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala index a979fe0f45..6c368cfa08 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -76,7 +76,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( case 0 ⇒ Result(defSite, StringConstancyProperty.getNeutralElement) case _ ⇒ val results = init.params.head.asVar.definedBy.map { ds: Int ⇒ - (ds, exprHandler.processDefSite(ds)) + (ds, exprHandler.processDefSite(ds, List())) } if (results.forall(_._2.isInstanceOf[Result])) { // Final result is available diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala index 658f88d99f..a0630e8fbe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -18,6 +18,7 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -59,6 +60,15 @@ class InterproceduralStaticFunctionCallInterpreter( val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, m) + // Collect all parameters; current assumption: Results of parameters are available right + // away + val paramScis = instr.params.map { p ⇒ + StringConstancyProperty.extractFromPPCR( + exprHandler.processDefSite(p.asVar.definedBy.head) + ).stringConstancyInformation + }.toList + InterproceduralStringAnalysis.registerParams(entity, paramScis) + val eps = ps(entity, StringConstancyProperty.key) eps match { case FinalEP(e, p) ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala index 2f666b93e8..f115b36d09 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala @@ -29,7 +29,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V class InterproceduralVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, - callees: Callees + callees: Callees, + params: List[StringConstancyInformation] ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -122,7 +123,8 @@ class InterproceduralVirtualFunctionCallInterpreter( ): StringConstancyProperty = { // There might be several receivers, thus the map; from the processed sites, however, use // only the head as a single receiver interpretation will produce one element - val scis = call.receiver.asVar.definedBy.toArray.sorted.map(exprHandler.processDefSite).map( + val defSites = call.receiver.asVar.definedBy.toArray.sorted + val scis = defSites.map(exprHandler.processDefSite(_, params)).map( _.asInstanceOf[Result].finalEP.p.asInstanceOf[StringConstancyProperty] ).filter { !_.stringConstancyInformation.isTheNeutralElement @@ -145,13 +147,13 @@ class InterproceduralVirtualFunctionCallInterpreter( // .head because we want to evaluate only the first argument of append val defSiteHead = param.definedBy.head var value = StringConstancyProperty.extractFromPPCR( - exprHandler.processDefSite(defSiteHead) + exprHandler.processDefSite(defSiteHead, params) ) // If defSiteHead points to a New, value will be the empty list. In that case, process // the first use site (which is the call) if (value.isTheNeutralElement) { value = StringConstancyProperty.extractFromPPCR(exprHandler.processDefSite( - cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min + cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min, params )) } @@ -199,7 +201,7 @@ class InterproceduralVirtualFunctionCallInterpreter( private def interpretToStringCall( call: VirtualFunctionCall[V] ): ProperPropertyComputationResult = - exprHandler.processDefSite(call.receiver.asVar.definedBy.head) + exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) /** * Function for processing calls to [[StringBuilder#replace]] or [[StringBuffer#replace]]. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala index 9d57cea345..947d7069ce 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala @@ -48,7 +48,7 @@ class IntraproceduralArrayInterpreter( stmts(_).isInstanceOf[ArrayStore[V]] } foreach { f: Int ⇒ val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - children.appendAll(sortedDefs.map { exprHandler.processDefSite }.map { n ⇒ + children.appendAll(sortedDefs.map { exprHandler.processDefSite(_) }.map { n ⇒ val r = n.asInstanceOf[Result] r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) @@ -61,7 +61,7 @@ class IntraproceduralArrayInterpreter( } } foreach { f: Int ⇒ val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - children.appendAll(defs.toArray.sorted.map { exprHandler.processDefSite }.map { n ⇒ + children.appendAll(defs.toArray.sorted.map(exprHandler.processDefSite(_)).map { n ⇒ val r = n.asInstanceOf[Result] r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala index f1531b2320..137e81d396 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -5,6 +5,7 @@ import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr @@ -43,7 +44,9 @@ class IntraproceduralInterpretationHandler( *

    * @inheritdoc */ - override def processDefSite(defSite: Int): ProperPropertyComputationResult = { + override def processDefSite( + defSite: Int, params: List[StringConstancyInformation] = List() + ): ProperPropertyComputationResult = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt From 6166760aeb57ab2a31914c61b2477740aa21aa6a Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Feb 2019 11:08:00 +0100 Subject: [PATCH 151/316] Slightly changed two test cases (1. to use more chars and 2. to use a negative number). --- .../fpcf/fixtures/string_analysis/LocalTestMethods.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index dae4d8e8bb..497cdf517f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -249,7 +249,7 @@ public void multipleOptionalAppendSites(int value) { expectedLevel = DYNAMIC, expectedStrings = "(x|[AnIntegerValue])" ), @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "(42|x)" + expectedLevel = CONSTANT, expectedStrings = "(42-42|x)" ) }) public void ifElseWithStringBuilderWithIntExpr() { @@ -259,6 +259,7 @@ public void ifElseWithStringBuilderWithIntExpr() { if (i % 2 == 0) { sb1.append("x"); sb2.append(42); + sb2.append(-42); } else { sb1.append(i + 1); sb2.append("x"); @@ -742,8 +743,8 @@ public void ifConditionAppendsToString(String className) { public void directAppendConcatsWith2ndStringBuilder() { StringBuilder sb = new StringBuilder("java"); StringBuilder sb2 = new StringBuilder("B"); - sb.append(".").append("lang"); - sb2.append("."); + sb.append('.').append("lang"); + sb2.append('.'); sb.append("String"); sb.append(sb2.toString()); analyzeString(sb2.toString()); From b359309111417401cf03b07c79837174592299a5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 12:34:30 +0100 Subject: [PATCH 152/316] Changed "[AnIntegerValue]" to its RegEx equivalent. --- .../fixtures/string_analysis/LocalTestMethods.java | 10 +++++++--- .../string_definition/StringConstancyInformation.scala | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index 497cdf517f..77908bfc80 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -36,6 +36,10 @@ *

  • * Brackets ("(" and "(") are used for nesting and grouping string expressions. *
  • + *
  • + * The string "-?\d+" represents (positive and negative) integer numbers. This RegExp has been taken + * from https://www.freeformatter.com/java-regex-tester.html#examples as of 2019-02-02. + *
  • * *

    * Thus, you should avoid the following characters / strings to occur in "expectedStrings": @@ -246,7 +250,7 @@ public void multipleOptionalAppendSites(int value) { + "and an int", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "(x|[AnIntegerValue])" + expectedLevel = DYNAMIC, expectedStrings = "(x|-?\\d+)" ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "(42-42|x)" @@ -347,7 +351,7 @@ public void simpleForLoopWithKnownBounds() { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|[AnIntegerValue]))*yz" + expectedStrings = "((x|-?\\d+))*yz" ) }) public void ifElseInLoopWithAppendAfterwards() { @@ -403,7 +407,7 @@ public void nestedLoops(int range) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|[AnIntegerValue]))*yz" + expectedStrings = "((x|-?\\d+))*yz" ) }) public void stringBufferExample() { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 159b60322f..0cccec79f8 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -41,7 +41,7 @@ object StringConstancyInformation { /** * The stringified version of a (dynamic) integer value. */ - val IntValue: String = "[AnIntegerValue]" + val IntValue: String = "-?\\d+" /** * The stringified version of a (dynamic) float value. From 6ff735591a974630674787ef80a58c5e41291fb1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 13:17:52 +0100 Subject: [PATCH 153/316] Changed "[AFloatValue]" to its corresponding RegEx and refined / extended the support for float and double values. --- .../string_analysis/LocalTestMethods.java | 27 ++++++++++++ .../StringConstancyInformation.scala | 2 +- .../DoubleValueInterpreter.scala | 44 +++++++++++++++++++ .../FloatValueInterpreter.scala | 44 +++++++++++++++++++ ...IntraproceduralInterpretationHandler.scala | 6 +++ ...eduralVirtualFunctionCallInterpreter.scala | 29 ++++++------ 6 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index 77908bfc80..64fe452a3b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -40,6 +40,11 @@ * The string "-?\d+" represents (positive and negative) integer numbers. This RegExp has been taken * from https://www.freeformatter.com/java-regex-tester.html#examples as of 2019-02-02. * + *

  • + * The string "-?\\d*\\.{0,1}\\d+" represents (positive and negative) float and double numbers. + * This RegExp has been taken from https://www.freeformatter.com/java-regex-tester.html#examples as + * of 2019-02-02. + *
  • * *

    * Thus, you should avoid the following characters / strings to occur in "expectedStrings": @@ -272,6 +277,28 @@ public void ifElseWithStringBuilderWithIntExpr() { analyzeString(sb2.toString()); } + @StringDefinitionsCollection( + value = "if-else control structure which append float and double values to a string " + + "builder", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "(3.14|-?\\d*\\.{0,1}\\d+)2.71828" + ) + }) + public void ifElseWithStringBuilderWithFloatExpr() { + StringBuilder sb1 = new StringBuilder(); + int i = new Random().nextInt(); + if (i % 2 == 0) { + sb1.append(3.14); + } else { + sb1.append(new Random().nextFloat()); + } + float e = (float) 2.71828; + sb1.append(e); + analyzeString(sb1.toString()); + } + @StringDefinitionsCollection( value = "if-else control structure which append to a string builder", stringDefinitions = { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 0cccec79f8..6e86521577 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -46,7 +46,7 @@ object StringConstancyInformation { /** * The stringified version of a (dynamic) float value. */ - val FloatValue: String = "[AFloatValue]" + val FloatValue: String = "-?\\d*\\.{0,1}\\d+" /** * A value to be used when the number of an element, that is repeated, is unknown. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala new file mode 100644 index 0000000000..cb80bc1b67 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala @@ -0,0 +1,44 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.DoubleConst +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * The `DoubleValueInterpreter` is responsible for processing [[DoubleConst]]s. + *

    + * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class DoubleValueInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = DoubleConst + + /** + * @note For this implementation, `defSite` does not play a role. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + ))) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala new file mode 100644 index 0000000000..8b3a3e63d9 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala @@ -0,0 +1,44 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.FloatConst + +/** + * The `FloatValueInterpreter` is responsible for processing [[FloatConst]]s. + *

    + * For this implementation, the concrete implementation passed for [[exprHandler]] is not relevant. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class FloatValueInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterpretationHandler +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = FloatConst + + /** + * @note For this implementation, `defSite` does not play a role. + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = + Result(instr, StringConstancyProperty(StringConstancyInformation( + StringConstancyLevel.CONSTANT, + StringConstancyType.APPEND, + instr.value.toString + ))) + +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala index 137e81d396..f3472ae9f4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -22,6 +22,8 @@ import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.DoubleConst +import org.opalj.tac.FloatConst /** * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are @@ -65,6 +67,10 @@ class IntraproceduralInterpretationHandler( new StringConstInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: IntConst) ⇒ new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) + case Assignment(_, _, expr: FloatConst) ⇒ + new FloatValueInterpreter(cfg, this).interpret(expr, defSite) + case Assignment(_, _, expr: DoubleConst) ⇒ + new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ new IntraproceduralArrayInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: New) ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala index 649efb179e..a727952bae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -11,6 +11,9 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.ComputationalTypeDouble +import org.opalj.br.DoubleType +import org.opalj.br.FloatType import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall @@ -68,6 +71,12 @@ class IntraproceduralVirtualFunctionCallInterpreter( instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ StringConstancyProperty.lb + case FloatType | DoubleType ⇒ + StringConstancyProperty(StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.FloatValue + )) case _ ⇒ StringConstancyProperty.getNeutralElement } } @@ -165,22 +174,14 @@ class IntraproceduralVirtualFunctionCallInterpreter( } else { sci } - case ComputationalTypeFloat ⇒ - InterpretationHandler.getConstancyInfoForDynamicFloat + case ComputationalTypeFloat | ComputationalTypeDouble ⇒ + if (sci.constancyLevel == StringConstancyLevel.CONSTANT) { + sci + } else { + InterpretationHandler.getConstancyInfoForDynamicFloat + } // Otherwise, try to compute case _ ⇒ - // It might be necessary to merge the values of the receiver and of the parameter - // value.size match { - // case 0 ⇒ None - // case 1 ⇒ Some(value.head) - // case _ ⇒ Some(StringConstancyInformation( - // StringConstancyLevel.determineForConcat( - // value.head.constancyLevel, value(1).constancyLevel - // ), - // StringConstancyType.APPEND, - // value.head.possibleStrings + value(1).possibleStrings - // )) - // } sci } From 7032a796a4c12d6a7c62c89cd91b348f270b5879 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 14:16:46 +0100 Subject: [PATCH 154/316] Changed one interprocedural test case in a way to use a method from another class. --- .../string_analysis/InterproceduralTestMethods.java | 9 +-------- .../fixtures/string_analysis/StringProvider.java | 13 +++++++++++++ .../scala/org/opalj/fpcf/StringAnalysisTest.scala | 3 ++- 3 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/StringProvider.java diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index b3bcf3cf06..cb5d5dac4d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -65,7 +65,7 @@ public void initFromNonVirtualFunctionCallTest(int i) { ) }) public void fromStaticMethodWithParam() { - analyzeString(InterproceduralTestMethods.getFQClassName(JAVA_LANG, "Integer")); + analyzeString(StringProvider.getFQClassName(JAVA_LANG, "Integer")); } private String getRuntimeClassName() { @@ -80,11 +80,4 @@ private String getSimpleStringBuilderClassName() { return "StringBuilder"; } - /** - * Returns "[packageName].[className]". - */ - public static String getFQClassName(String packageName, String className) { - return packageName + "." + className; - } - } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/StringProvider.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/StringProvider.java new file mode 100644 index 0000000000..59ba4b4573 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/StringProvider.java @@ -0,0 +1,13 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis; + +public class StringProvider { + + /** + * Returns "[packageName].[className]". + */ + public static String getFQClassName(String packageName, String className) { + return packageName + "." + className; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 1e296e769b..69ef2cb98e 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -254,7 +254,8 @@ object InterproceduralStringAnalysisTest { val nameTestMethod = "analyzeString" // Files to load for the runner val filesToLoad = List( - "fixtures/string_analysis/InterproceduralTestMethods.class" + "fixtures/string_analysis/InterproceduralTestMethods.class", + "fixtures/string_analysis/StringProvider.class" ) } From b59f41c313eac94bd7a9019288a2d6ca6768d147 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 14:25:52 +0100 Subject: [PATCH 155/316] Wrapped the regular expressions in "^" and "$" (to indicate that really a RegExp follows). --- .../fixtures/string_analysis/LocalTestMethods.java | 12 ++++++------ .../StringConstancyInformation.scala | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index 64fe452a3b..f3bb4363bd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -37,11 +37,11 @@ * Brackets ("(" and "(") are used for nesting and grouping string expressions. * *

  • - * The string "-?\d+" represents (positive and negative) integer numbers. This RegExp has been taken + * The string "^-?\d+$" represents (positive and negative) integer numbers. This RegExp has been taken * from https://www.freeformatter.com/java-regex-tester.html#examples as of 2019-02-02. *
  • *
  • - * The string "-?\\d*\\.{0,1}\\d+" represents (positive and negative) float and double numbers. + * The string "^-?\\d*\\.{0,1}\\d+$" represents (positive and negative) float and double numbers. * This RegExp has been taken from https://www.freeformatter.com/java-regex-tester.html#examples as * of 2019-02-02. *
  • @@ -255,7 +255,7 @@ public void multipleOptionalAppendSites(int value) { + "and an int", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "(x|-?\\d+)" + expectedLevel = DYNAMIC, expectedStrings = "(x|^-?\\d+$)" ), @StringDefinitions( expectedLevel = CONSTANT, expectedStrings = "(42-42|x)" @@ -283,7 +283,7 @@ public void ifElseWithStringBuilderWithIntExpr() { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(3.14|-?\\d*\\.{0,1}\\d+)2.71828" + expectedStrings = "(3.14|^-?\\d*\\.{0,1}\\d+$)2.71828" ) }) public void ifElseWithStringBuilderWithFloatExpr() { @@ -378,7 +378,7 @@ public void simpleForLoopWithKnownBounds() { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|-?\\d+))*yz" + expectedStrings = "((x|^-?\\d+$))*yz" ) }) public void ifElseInLoopWithAppendAfterwards() { @@ -434,7 +434,7 @@ public void nestedLoops(int range) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "((x|-?\\d+))*yz" + expectedStrings = "((x|^-?\\d+$))*yz" ) }) public void stringBufferExample() { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 6e86521577..4f7b18f2d1 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -41,12 +41,12 @@ object StringConstancyInformation { /** * The stringified version of a (dynamic) integer value. */ - val IntValue: String = "-?\\d+" + val IntValue: String = "^-?\\d+$" /** * The stringified version of a (dynamic) float value. */ - val FloatValue: String = "-?\\d*\\.{0,1}\\d+" + val FloatValue: String = "^-?\\d*\\.{0,1}\\d+$" /** * A value to be used when the number of an element, that is repeated, is unknown. From 346eb3ebcfe8242c3977761a8d18e85a6da91ca6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 16:58:41 +0100 Subject: [PATCH 156/316] Added test cases where methods are called that are out of scope and thus not interpreted. --- .../InterproceduralTestMethods.java | 41 ++++++++++++++++++- .../AbstractStringInterpreter.scala | 16 ++++++-- ...ralNonVirtualFunctionCallInterpreter.scala | 9 +++- ...ceduralStaticFunctionCallInterpreter.scala | 13 ++++-- .../preprocessing/PathTransformer.scala | 2 +- 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index cb5d5dac4d..f6dfe1a4ac 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -4,7 +4,12 @@ import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; + import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.CONSTANT; +import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.DYNAMIC; /** * This file contains various tests for the InterproceduralStringAnalysis. For further information @@ -64,10 +69,44 @@ public void initFromNonVirtualFunctionCallTest(int i) { expectedStrings = "java.lang.Integer" ) }) - public void fromStaticMethodWithParam() { + public void fromStaticMethodWithParamTest() { analyzeString(StringProvider.getFQClassName(JAVA_LANG, "Integer")); } + @StringDefinitionsCollection( + value = "a case where a static method is called that returns a string but are not " + + "within this project => cannot / will not interpret", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" + ), + + }) + public void staticMethodOutOfScopeTest() throws FileNotFoundException { + analyzeString(System.getProperty("os.version")); + } + + @StringDefinitionsCollection( + value = "a case where a (virtual) method is called that return a string but are not " + + "within this project => cannot / will not interpret", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "(\\w)*" + ) + + }) + public void methodOutOfScopeTest() throws FileNotFoundException { + File file = new File("my-file.txt"); + Scanner sc = new Scanner(file); + StringBuilder sb = new StringBuilder(); + while (sc.hasNextLine()) { + sb.append(sc.nextLine()); + } + analyzeString(sb.toString()); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index ed5905feb4..9c4e6bc18e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -8,6 +8,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.DefinedMethod import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -63,10 +64,19 @@ abstract class AbstractStringInterpreter( /** * Takes `declaredMethods` as well as a method `name`, extracts the method with the given `name` - * from `declaredMethods` and returns this one as a [[Method]]. + * from `declaredMethods` and returns this one as a [[Method]]. It might be, that the given + * method cannot be found. In these cases, `None` will be returned. */ - protected def getDeclaredMethod(declaredMethods: DeclaredMethods, name: String): Method = - declaredMethods.declaredMethods.find(_.name == name).get.definedMethod + protected def getDeclaredMethod( + declaredMethods: DeclaredMethods, name: String + ): Option[Method] = { + val dm = declaredMethods.declaredMethods.find(_.name == name) + if (dm.isDefined && dm.get.isInstanceOf[DefinedMethod]) { + Some(dm.get.definedMethod) + } else { + None + } + } /** * diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala index 2312074ac0..260a815f50 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -50,7 +50,14 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val m = getDeclaredMethod(declaredMethods, instr.name) + val methodOption = getDeclaredMethod(declaredMethods, instr.name) + + if (methodOption.isEmpty) { + val e: Integer = defSite + return Result(e, StringConstancyProperty.lb) + } + + val m = methodOption.get val tac = getTACAI(ps, m, state) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala index a0630e8fbe..d3487f8c41 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala @@ -52,7 +52,14 @@ class InterproceduralStaticFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val m = getDeclaredMethod(declaredMethods, instr.name) + val methodOption = getDeclaredMethod(declaredMethods, instr.name) + + if (methodOption.isEmpty) { + val e: Integer = defSite + return Result(e, StringConstancyProperty.lb) + } + + val m = methodOption.get val tac = getTACAI(ps, m, state) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis @@ -60,8 +67,8 @@ class InterproceduralStaticFunctionCallInterpreter( val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, m) - // Collect all parameters; current assumption: Results of parameters are available right - // away + // Collect all parameters + // TODO: Current assumption: Results of parameters are available right away val paramScis = instr.params.map { p ⇒ StringConstancyProperty.extractFromPPCR( exprHandler.processDefSite(p.asVar.definedBy.head) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 5c2cdd5c37..36c2f49599 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -50,7 +50,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { npe.elementType.get match { case NestedPathType.Repetition ⇒ val processedSubPath = pathToStringTree( - Path(npe.element.toList), resetExprHandler = false + Path(npe.element.toList), fpe2Sci, resetExprHandler = false ) Some(StringTreeRepetition(processedSubPath)) case _ ⇒ From d137145a7d68eaa683008ab7a295bdb068b3f60b Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 19:32:55 +0100 Subject: [PATCH 157/316] Formatted the file + fixed a typo. --- .../test/scala/org/opalj/fpcf/StringAnalysisTest.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 69ef2cb98e..2ba3276b03 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -152,8 +152,8 @@ sealed class StringAnalysisTestRunner( } /** - * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some well-defined - * tests. + * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some + * well-defined tests. * * @author Patrick Mell */ @@ -196,8 +196,8 @@ object IntraproceduralStringAnalysisTest { } /** - * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some well-defined - * tests. + * Tests whether the [[InterproceduralStringAnalysis]] works correctly with respect to some + * well-defined tests. * * @author Patrick Mell */ From 0ace577bec022ab1a4a3bdcb18a1d91429fd1de8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Feb 2019 19:33:56 +0100 Subject: [PATCH 158/316] Extended a function call. --- .../string_analysis/InterproceduralStringAnalysis.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 4dbfe10bb7..bc8ec23b23 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -307,7 +307,9 @@ class InterproceduralStringAnalysis( } } case npe: NestedPathElement ⇒ - val subFinalResult = computeResultsForPath(Path(List(npe)), iHandler, state) + val subFinalResult = computeResultsForPath( + Path(npe.element.toList), iHandler, state + ) if (hasFinalResult) { hasFinalResult = subFinalResult } From aaf10910bf26936b0bd5df41d05e63e09a4812e4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 08:51:30 +0100 Subject: [PATCH 159/316] Removed ToDos (I guess they cannot be done as I thought). --- .../interpretation/InterproceduralInterpretationHandler.scala | 2 -- .../interpretation/IntraproceduralInterpretationHandler.scala | 2 -- 2 files changed, 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 59d9704dd4..072dd99eea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -72,8 +72,6 @@ class InterproceduralInterpretationHandler( processedDefSites.append(defSite) val callees = state.callees.get - // TODO: Refactor by making the match return a concrete instance of - // AbstractStringInterpreter on which 'interpret' is the called only once stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ new StringConstInterpreter(cfg, this).interpret(expr, defSite) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala index f3472ae9f4..5fd17bddfc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala @@ -60,8 +60,6 @@ class IntraproceduralInterpretationHandler( } processedDefSites.append(defSite) - // TODO: Refactor by making the match return a concrete instance of - // AbstractStringInterpreter on which 'interpret' is the called only once val result: ProperPropertyComputationResult = stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ new StringConstInterpreter(cfg, this).interpret(expr, defSite) From b07cf7fecb095dbef578b1c7c4e5c7b5f137b9e9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 08:53:46 +0100 Subject: [PATCH 160/316] Extended the interprocedural analysis to better handle float / double values. --- .../InterproceduralInterpretationHandler.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index 072dd99eea..b605508491 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -26,6 +26,8 @@ import org.opalj.tac.StringConst import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.DoubleConst +import org.opalj.tac.FloatConst /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -77,6 +79,10 @@ class InterproceduralInterpretationHandler( new StringConstInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: IntConst) ⇒ new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) + case Assignment(_, _, expr: FloatConst) ⇒ + new FloatValueInterpreter(cfg, this).interpret(expr, defSite) + case Assignment(_, _, expr: DoubleConst) ⇒ + new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ new InterproceduralArrayInterpreter(cfg, this, callees).interpret(expr, defSite) case Assignment(_, _, expr: New) ⇒ From c515d4f56835bf4072061a53d02b097202c1401b Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 09:24:47 +0100 Subject: [PATCH 161/316] Added a comment describing the high-level approach of this analysis. --- .../IntraproceduralStringAnalysis.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index 804a743d76..e9d14a7b03 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -41,10 +41,24 @@ import org.opalj.tac.fpcf.properties.TACAI /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. - * + *

    * This analysis takes into account only the enclosing function as a context, i.e., it * intraprocedural. Values coming from other functions are regarded as dynamic values even if the * function returns a constant string value. + *

    + * From a high-level perspective, this analysis works as follows. First, it has to be differentiated + * whether string literals / variables or String{Buffer, Builder} are to be processed. + * For the former, the definition sites are processed. Only one definition site is the trivial case + * and directly corresponds to a leaf node in the string tree (such trees consist of only one node). + * Multiple definition sites indicate > 1 possible initialization values and are transformed into a + * string tree whose root node is an OR element and the children are the possible initialization + * values. Note that all this is handled by [[StringConstancyInformation.reduceMultiple]]. + *

    + * For the latter, String{Buffer, Builder}, lean paths from the definition sites to the usage + * (indicated by the given DUVar) is computed. That is, all paths from all definition sites to the + * usage where only statements are contained that include the String{Builder, Buffer} object of + * interest in some way (like an "append" or "replace" operation for example). These paths are then + * transformed into a string tree by making use of a [[PathTransformer]]. * * @author Patrick Mell */ From c53593a4c747e1dd132b097f42bceb90fe3921b8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 09:28:27 +0100 Subject: [PATCH 162/316] Added a comment describing the high-level approach of this analysis. --- .../InterproceduralStringAnalysis.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index bc8ec23b23..3e2eedce9e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -68,9 +68,25 @@ case class ComputationState( /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program * position, ''pp'', in a way that it finds the set of possible strings that can be read at ''pp''. - * + *

    * In comparison to [[IntraproceduralStringAnalysis]], this version tries to resolve method calls * that are involved in a string construction as far as possible. + *

    + * The main difference in the intra- and interprocedural implementation is the following (see the + * description of [[IntraproceduralStringAnalysis]] for a general overview): This analysis can only + * start to transform the computed lean paths into a string tree (again using a [[PathTransformer]]) + * after all relevant string values (determined by the [[InterproceduralInterpretationHandler]]) + * have been figured out. As the [[PropertyStore]] is used for recursively starting this analysis + * to determine possible strings of called method and functions, the path transformation can take + * place after all results for sub-expressions are available. Thus, the interprocedural + * interpretation handler cannot determine final results, e.g., for the array interpreter or static + * function call interpreter. This analysis handles this circumstance by first collecting all + * information for all definition sites. Only when these are available, further information, e.g., + * for the final results of arrays or static function calls, are derived. Finally, after all + * these information are ready as well, the path transformation takes place by only looking up what + * string expression corresponds to which definition sites (remember, at this point, for all + * definition sites all possible string values are known, thus look-ups are enough and no further + * interpretation is required). * * @author Patrick Mell */ From 5f6d03982cf61925039468272a77de71b3fec0df Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Feb 2019 21:08:39 +0100 Subject: [PATCH 163/316] Extended the interprocedural analysis to finalize results of expressions where sub results have to be computed first. Corresponding test cases (initFromNonVirtualFunctionCallTest, arrayTest) were added. --- .../InterproceduralTestMethods.java | 36 ++++- .../InterproceduralStringAnalysis.scala | 151 ++++++++++++------ .../interpretation/ArrayFinalizer.scala | 55 +++++++ .../ArrayPreparationInterpreter.scala | 111 +++++++++++++ .../InterproceduralArrayInterpreter.scala | 82 ---------- ...InterproceduralInterpretationHandler.scala | 45 +++++- ...duralNonVirtualMethodCallInterpreter.scala | 5 +- .../NonVirtualMethodCallFinalizer.scala | 23 +++ 8 files changed, 363 insertions(+), 145 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index f6dfe1a4ac..723a8305c3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -31,12 +31,20 @@ public void analyzeString(String s) { value = "a case where a very simple non-virtual function call is interpreted", stringDefinitions = { @StringDefinitions( - expectedLevel = CONSTANT, expectedStrings = "java.lang.StringBuilder" + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|ERROR)" ) }) - public void simpleNonVirtualFunctionCallTest() { - String className = getStringBuilderClassName(); - analyzeString(className); + public void simpleNonVirtualFunctionCallTest(int i) { + String s; + if (i == 0) { + s = getRuntimeClassName(); + } else if (i == 1) { + s = getStringBuilderClassName(); + } else { + s = "ERROR"; + } + analyzeString(s); } @StringDefinitionsCollection( @@ -107,6 +115,26 @@ public void methodOutOfScopeTest() throws FileNotFoundException { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "a case where an array access needs to be interpreted interprocedurally", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "(java.lang.Object|java.lang.Runtime|" + + "java.lang.Integer|\\w)" + ) + + }) + public void arrayTest(int i) { + String[] classes = { + "java.lang.Object", + getRuntimeClassName(), + StringProvider.getFQClassName("java.lang", "Integer"), + System.getProperty("SomeClass") + }; + analyzeString(classes[i]); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 3e2eedce9e..5ea681549d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -39,6 +39,7 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath /** @@ -47,22 +48,46 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath * have all required information ready for a final result. */ case class ComputationState( - // The lean path that was computed var computedLeanPath: Option[Path], - // The control flow graph on which the computedLeanPath is based - cfg: CFG[Stmt[V], TACStmts[V]], - // - var callees: Option[Callees] = None + cfg: CFG[Stmt[V], TACStmts[V]], + var callees: Option[Callees] = None ) { // If not empty, this very routine can only produce an intermediate result - // TODO: The value must be a list as one entity can have multiple dependees! val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() // A mapping from DUVar elements to the corresponding indices of the FlatPathElements val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() - // A mapping from values of FlatPathElements to StringConstancyInformation + // A mapping from values / indices of FlatPathElements to StringConstancyInformation val fpe2sci: mutable.Map[Int, StringConstancyInformation] = mutable.Map() - + // Some interpreters, such as the array interpreter, need preparation (see their comments for + // more information). This map stores the prepared data. The key of the outer map is the + // instruction that is to be interpreted. The key of the inner map is a definition site and the + // inner maps value the associated string constancy information + // val preparationSciInformation: mutable.Map[Any, mutable.Map[Int, StringConstancyInformation]] = + // mutable.Map() + // Parameter values of method / function; a mapping from the definition sites of parameter ( + // negative values) to a correct index of `params` has to be made! var params: List[StringConstancyInformation] = List() + + /** + * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly. + */ + def appendResultToFpe2Sci(defSite: Int, r: Result): Unit = { + val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + fpe2sci(defSite) = sci + } + + /** + * addPreparationInformation is responsible for adding an entry to preparationSciInformation + */ + // def addPreparationInformation( + // instr: Any, defSite: Int, sci: StringConstancyInformation + // ): Unit = { + // if (!preparationSciInformation.contains(instr)) { + // preparationSciInformation(instr) = mutable.Map() + // } + // preparationSciInformation(instr)(defSite) = sci + // } + } /** @@ -159,7 +184,7 @@ class InterproceduralStringAnalysis( val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ - return processFinalP(data, callees, state, ep.e, p) + return processFinalP(data, state, ep.e, p) case _ ⇒ if (!state.dependees.contains(toAnalyze)) { state.dependees(toAnalyze) = ListBuffer() @@ -179,24 +204,26 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { + val leanPath = if (defSites.length == 1) { + // Trivial case for just one element + Path(List(FlatPathElement(defSites.head))) + } else { + // For > 1 definition sites, create a nest path element with |defSites| many + // children where each child is a NestPathElement(FlatPathElement) + val children = ListBuffer[SubPath]() + defSites.foreach { ds ⇒ + children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) + } + Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) + } + val interHandler = InterproceduralInterpretationHandler( cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) ) - val results = uvar.definedBy.toArray.sorted.map { ds ⇒ - (ds, interHandler.processDefSite(ds, state.params)) - } - val interimResults = results.filter(!_._2.isInstanceOf[Result]).map { r ⇒ - (r._1, r._2.asInstanceOf[InterimResult[StringConstancyProperty]]) - } - if (interimResults.isEmpty) { - // All results are available => Prepare the final result - sci = StringConstancyInformation.reduceMultiple( - results.map { - case (_, r) ⇒ - val p = r.asInstanceOf[Result].finalEP.p - p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }.toList - ) + state.computedLeanPath = Some(leanPath) + if (computeResultsForPath(leanPath, interHandler, state)) { + // All necessary information are available => Compute final result + return computeFinalResult(data, state, interHandler) } // No need to cover the else branch: interimResults.nonEmpty => dependees were added to // state.dependees, i.e., the if that checks whether state.dependees is non-empty will @@ -228,16 +255,53 @@ class InterproceduralStringAnalysis( case _ ⇒ throw new IllegalStateException("can occur?") } + private def finalizePreparations( + path: Path, state: ComputationState, iHandler: InterproceduralInterpretationHandler + ): Unit = { + path.elements.foreach { + case FlatPathElement(index) ⇒ + if (!state.fpe2sci.contains(index)) { + iHandler.finalizeDefSite(index, state) + } + case npe: NestedPathElement ⇒ + finalizePreparations(Path(npe.element.toList), state, iHandler) + case _ ⇒ + } + } + + /** + * computeFinalResult computes the final result of an analysis. This includes the computation + * of instruction that could only be prepared (e.g., if an array load included a method call, + * its final result is not yet ready, however, this function finalizes, e.g., that load). + * + * @param data The entity that was to analyze. + * @param state The final computation state. For this state the following criteria must apply: + * For each [[FlatPathElement]], there must be a corresponding entry in + * [[state.fpe2sci]]. If this criteria is not met, a [[NullPointerException]] will + * be thrown (in this case there was some work to do left and this method should + * not have been called)! + * @return Returns the final result. + */ + private def computeFinalResult( + data: P, state: ComputationState, iHandler: InterproceduralInterpretationHandler + ): Result = { + finalizePreparations(state.computedLeanPath.get, state, iHandler) + val finalSci = new PathTransformer(null).pathToStringTree( + state.computedLeanPath.get, state.fpe2sci.toMap, resetExprHandler = false + ).reduce(true) + Result(data, StringConstancyProperty(finalSci)) + } + /** * `processFinalP` is responsible for handling the case that the `propertyStore` outputs a * [[org.opalj.fpcf.FinalP]]. */ private def processFinalP( - data: P, - callees: Callees, - state: ComputationState, - e: Entity, - p: Property + data: P, + // callees: Callees, + state: ComputationState, + e: Entity, + p: Property ): ProperPropertyComputationResult = { // Add mapping information (which will be used for computing the final result) val retrievedProperty = p.asInstanceOf[StringConstancyProperty] @@ -248,27 +312,18 @@ class InterproceduralStringAnalysis( state.dependees.foreach { case (k, v) ⇒ state.dependees(k) = v.filter(_.e != e) } val remDependees = state.dependees.values.flatten if (remDependees.isEmpty) { - // This is the case if the string information stems from a String{Builder, Buffer} - val finalSci = if (state.computedLeanPath.isDefined) { - val interpretationHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, - continuation(data, callees, List(), state) - ) - new PathTransformer(interpretationHandler).pathToStringTree( - state.computedLeanPath.get, state.fpe2sci.toMap - ).reduce(true) - } else { - // This is the case if the string information stems from a String variable - currentSci - } - Result(data, StringConstancyProperty(finalSci)) + val iHandler = InterproceduralInterpretationHandler( + state.cfg, ps, declaredMethods, state, + continuation(data, state.callees.get, List(), state) + ) + computeFinalResult(data, state, iHandler) } else { InterimResult( data, StringConstancyProperty.ub, StringConstancyProperty.lb, remDependees, - continuation(data, callees, remDependees, state) + continuation(data, state.callees.get, remDependees, state) ) } } @@ -288,7 +343,7 @@ class InterproceduralStringAnalysis( dependees: Iterable[EOptionP[Entity, Property]], state: ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case FinalP(p) ⇒ processFinalP(data, callees, state, eps.e, p) + case FinalP(p) ⇒ processFinalP(data, state, eps.e, p) case InterimLUBP(lb, ub) ⇒ InterimResult( data, lb, ub, dependees, continuation(data, callees, dependees, state) ) @@ -296,12 +351,12 @@ class InterproceduralStringAnalysis( } /** - * This function traversed the given path, computes all string values along the path and stores + * This function traverses the given path, computes all string values along the path and stores * these information in the given state. * * @param p The path to traverse. * @param iHandler The handler for interpreting string related sites. - * @param state The current state of the computation. This function will extend + * @param state The current state of the computation. This function will alter * [[ComputationState.fpe2sci]]. * @return Returns `true` if all values computed for the path are final results. */ @@ -430,7 +485,7 @@ object InterproceduralStringAnalysis { if (!paramInfos.contains(e)) { paramInfos(e) = List(scis: _*) } - // Per entity and method, a StringConstancyInformation list sshoud be present only once, + // Per entity and method, a StringConstancyInformation list should be present only once, // thus no else branch } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala new file mode 100644 index 0000000000..87ded50a37 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala @@ -0,0 +1,55 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import scala.collection.mutable.ListBuffer + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.ArrayLoad +import org.opalj.tac.ArrayStore +import org.opalj.tac.Assignment +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts + +/** + * @author Patrick Mell + */ +class ArrayFinalizer(cfg: CFG[Stmt[V], TACStmts[V]], state: ComputationState) { + + type T = ArrayLoad[V] + + def interpret( + instr: T, defSite: Int + ): Unit = { + val stmts = cfg.code.instructions + val allDefSites = ListBuffer[Int]() + val defSites = instr.arrayRef.asVar.definedBy.toArray + defSites.filter(_ >= 0).sorted.foreach { next ⇒ + val arrDecl = stmts(next) + val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + // Process ArrayStores + sortedArrDeclUses.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) + } + // Process ArrayLoads + sortedArrDeclUses.filter { + stmts(_) match { + case Assignment(_, _, _: ArrayLoad[V]) ⇒ true + case _ ⇒ false + } + } foreach { f: Int ⇒ + val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy + allDefSites.appendAll(defs.toArray) + } + } + + state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple( + allDefSites.sorted.map { state.fpe2sci(_) }.toList + ) + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala new file mode 100644 index 0000000000..1fbc49a119 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala @@ -0,0 +1,111 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.ArrayLoad +import org.opalj.tac.ArrayStore +import org.opalj.tac.Assignment +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * The `ArrayPreparationInterpreter` is responsible for preparing [[ArrayLoad]] as well as + * [[ArrayStore]] expressions in an interprocedural fashion. + *

    + * Not all (partial) results are guaranteed to be available at once, thus intermediate results + * might be produced. This interpreter will only compute the parts necessary to later on fully + * assemble the final result for the array interpretation. + * For more information, see the [[interpret]] method. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class ArrayPreparationInterpreter( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + state: ComputationState, + params: List[StringConstancyInformation] +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = ArrayLoad[V] + + /** + * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string + * constancy information foreach definition site where it can compute a final result. All + * definition sites producing an intermediate result will have to be handled later on to + * not miss this information. + * + * @note For this implementation, `defSite` plays a role! + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + val stmts = cfg.code.instructions + val results = ListBuffer[ProperPropertyComputationResult]() + + // Loop over all possible array values + val allDefSites = ListBuffer[Int]() + val defSites = instr.arrayRef.asVar.definedBy.toArray + defSites.filter(_ >= 0).sorted.foreach { next ⇒ + val arrDecl = stmts(next) + val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + // Process ArrayStores + sortedArrDeclUses.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) + } + // Process ArrayLoads + sortedArrDeclUses.filter { + stmts(_) match { + case Assignment(_, _, _: ArrayLoad[V]) ⇒ true + case _ ⇒ false + } + } foreach { f: Int ⇒ + val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy + allDefSites.appendAll(defs.toArray) + } + } + + allDefSites.sorted.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { + case (ds, r: Result) ⇒ + state.appendResultToFpe2Sci(ds, r) + results.append(r) + case (_, ir: ProperPropertyComputationResult) ⇒ results.append(ir) + } + + // Add information of parameters + defSites.filter(_ < 0).foreach { ds ⇒ + val paramPos = Math.abs(defSite + 2) + // lb is the fallback value + var sci = StringConstancyInformation( + possibleStrings = StringConstancyInformation.UnknownWordSymbol + ) + if (paramPos < params.size) { + sci = params(paramPos) + } + val e: Integer = ds + state.appendResultToFpe2Sci(ds, Result(e, StringConstancyProperty(sci))) + } + + // If there is at least one InterimResult, return one. Otherwise, return a final result + // (to either indicate that further computation are necessary or a final result is already + // present) + val interimResult = results.find(!_.isInstanceOf[Result]) + if (interimResult.isDefined) { + interimResult.get + } else { + results.head + } + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala deleted file mode 100644 index 1fc6f5acc4..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralArrayInterpreter.scala +++ /dev/null @@ -1,82 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation - -import scala.collection.mutable.ListBuffer - -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.ArrayLoad -import org.opalj.tac.ArrayStore -import org.opalj.tac.Assignment -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.V - -/** - * The `InterproceduralArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as - * [[ArrayStore]] expressions in an interprocedural fashion. - * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell - */ -class InterproceduralArrayInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees -) extends AbstractStringInterpreter(cfg, exprHandler) { - - override type T = ArrayLoad[V] - - /** - * @note For this implementation, `defSite` plays a role! - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - // TODO: Change from intra- to interprocedural - val stmts = cfg.code.instructions - val children = ListBuffer[StringConstancyInformation]() - // Loop over all possible array values - val defSites = instr.arrayRef.asVar.definedBy.toArray - defSites.filter(_ >= 0).sorted.foreach { next ⇒ - val arrDecl = stmts(next) - val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted - // Process ArrayStores - sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int ⇒ - val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted - children.appendAll(sortedDefs.map { exprHandler.processDefSite(_) }.map { - _.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }) - } - // Process ArrayLoads - sortedArrDeclUses.filter { - stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) ⇒ true - case _ ⇒ false - } - } foreach { f: Int ⇒ - val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - children.appendAll(defs.toArray.sorted.map { exprHandler.processDefSite(_) }.map { - _.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }) - } - } - - // In case it refers to a method parameter, add a dynamic string property - if (defSites.exists(_ < 0)) { - children.append(StringConstancyProperty.lb.stringConstancyInformation) - } - - Result(instr, StringConstancyProperty( - StringConstancyInformation.reduceMultiple(children.toList) - )) - } - -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala index b605508491..b66b4d2892 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala @@ -76,17 +76,27 @@ class InterproceduralInterpretationHandler( val callees = state.callees.get stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ - new StringConstInterpreter(cfg, this).interpret(expr, defSite) + val result = new StringConstInterpreter(cfg, this).interpret(expr, defSite) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result case Assignment(_, _, expr: IntConst) ⇒ - new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) + val result = new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result case Assignment(_, _, expr: FloatConst) ⇒ - new FloatValueInterpreter(cfg, this).interpret(expr, defSite) + val result = new FloatValueInterpreter(cfg, this).interpret(expr, defSite) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result case Assignment(_, _, expr: DoubleConst) ⇒ - new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) + val result = new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new InterproceduralArrayInterpreter(cfg, this, callees).interpret(expr, defSite) + new ArrayPreparationInterpreter(cfg, this, state, params).interpret(expr, defSite) case Assignment(_, _, expr: New) ⇒ - new NewInterpreter(cfg, this).interpret(expr, defSite) + val result = new NewInterpreter(cfg, this).interpret(expr, defSite) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ new InterproceduralVirtualFunctionCallInterpreter( cfg, this, callees, params @@ -96,7 +106,9 @@ class InterproceduralInterpretationHandler( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) case Assignment(_, _, expr: BinaryExpr[V]) ⇒ - new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) + val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ new InterproceduralNonVirtualFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c @@ -116,13 +128,30 @@ class InterproceduralInterpretationHandler( cfg, this, callees ).interpret(vmc, defSite) case nvmc: NonVirtualMethodCall[V] ⇒ - new InterproceduralNonVirtualMethodCallInterpreter( + val result = new InterproceduralNonVirtualMethodCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(nvmc, defSite) + result match { + case r: Result ⇒ state.appendResultToFpe2Sci(defSite, r) + case _ ⇒ + } + result case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } } + def finalizeDefSite( + defSite: Int, state: ComputationState + ): Unit = { + stmts(defSite) match { + case nvmc: NonVirtualMethodCall[V] ⇒ + new NonVirtualMethodCallFinalizer(state).interpret(nvmc, defSite) + case Assignment(_, _, expr: ArrayLoad[V]) ⇒ + new ArrayFinalizer(cfg, state).interpret(expr, defSite) + case _ ⇒ + } + } + } object InterproceduralInterpretationHandler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala index 6c368cfa08..ac5253ef75 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -89,9 +89,8 @@ class InterproceduralNonVirtualMethodCallInterpreter( // results and return an intermediate result val returnIR = results.find(r ⇒ !r._2.isInstanceOf[Result]).get._2 results.foreach { - case (ds, Result(r)) ⇒ - val p = r.p.asInstanceOf[StringConstancyProperty] - state.fpe2sci(ds) = p.stringConstancyInformation + case (ds, r: Result) ⇒ + state.appendResultToFpe2Sci(ds, r) case _ ⇒ } // TODO: is it enough to return only one (the first) IntermediateResult in case diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala new file mode 100644 index 0000000000..78d11b08e4 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation + +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.V + +/** + * @author Patrick Mell + */ +class NonVirtualMethodCallFinalizer(state: ComputationState) { + + type T = NonVirtualMethodCall[V] + + def interpret( + instr: T, defSite: Int + ): Unit = { + val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } + state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple(scis.toList) + } + +} From 37e8bb4e4fd7d0e6de576b6cdb09c051c3fa67e3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 09:19:04 +0100 Subject: [PATCH 164/316] Improved the structure of the interpretation part by creating corresponding packages. --- .../InterproceduralStringAnalysis.scala | 2 +- .../IntraproceduralStringAnalysis.scala | 2 +- .../{ => common}/BinaryExprInterpreter.scala | 4 +++- .../{ => common}/DoubleValueInterpreter.scala | 4 +++- .../{ => common}/FloatValueInterpreter.scala | 4 +++- .../{ => common}/IntegerValueInterpreter.scala | 4 +++- .../{ => common}/NewInterpreter.scala | 4 +++- .../{ => common}/StringConstInterpreter.scala | 4 +++- .../{ => finalizer}/ArrayFinalizer.scala | 2 +- .../NonVirtualMethodCallFinalizer.scala | 2 +- .../ArrayPreparationInterpreter.scala | 3 ++- .../InterproceduralFieldInterpreter.scala | 3 ++- .../InterproceduralGetStaticInterpreter.scala | 4 +++- .../InterproceduralInterpretationHandler.scala | 16 +++++++++++++--- ...eduralNonVirtualFunctionCallInterpreter.scala | 3 ++- ...oceduralNonVirtualMethodCallInterpreter.scala | 3 ++- ...proceduralStaticFunctionCallInterpreter.scala | 3 ++- ...roceduralVirtualFunctionCallInterpreter.scala | 4 +++- ...rproceduralVirtualMethodCallInterpreter.scala | 3 ++- .../IntraproceduralArrayInterpreter.scala | 3 ++- .../IntraproceduralFieldInterpreter.scala | 3 ++- .../IntraproceduralGetStaticInterpreter.scala | 3 ++- .../IntraproceduralInterpretationHandler.scala | 12 ++++++++++-- ...eduralNonVirtualFunctionCallInterpreter.scala | 3 ++- ...oceduralNonVirtualMethodCallInterpreter.scala | 3 ++- ...proceduralStaticFunctionCallInterpreter.scala | 3 ++- ...roceduralVirtualFunctionCallInterpreter.scala | 4 +++- ...aproceduralVirtualMethodCallInterpreter.scala | 3 ++- .../preprocessing/PathTransformer.scala | 6 +++--- 29 files changed, 83 insertions(+), 34 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => common}/BinaryExprInterpreter.scala (91%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => common}/DoubleValueInterpreter.scala (89%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => common}/FloatValueInterpreter.scala (89%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => common}/IntegerValueInterpreter.scala (89%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => common}/NewInterpreter.scala (88%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => common}/StringConstInterpreter.scala (90%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => finalizer}/ArrayFinalizer.scala (99%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => finalizer}/NonVirtualMethodCallFinalizer.scala (98%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/ArrayPreparationInterpreter.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralFieldInterpreter.scala (94%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralGetStaticInterpreter.scala (85%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralInterpretationHandler.scala (87%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralNonVirtualFunctionCallInterpreter.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralNonVirtualMethodCallInterpreter.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralStaticFunctionCallInterpreter.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralVirtualFunctionCallInterpreter.scala (98%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/InterproceduralVirtualMethodCallInterpreter.scala (95%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralArrayInterpreter.scala (96%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralFieldInterpreter.scala (93%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralGetStaticInterpreter.scala (93%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralInterpretationHandler.scala (87%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralNonVirtualFunctionCallInterpreter.scala (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralNonVirtualMethodCallInterpreter.scala (96%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralStaticFunctionCallInterpreter.scala (92%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralVirtualFunctionCallInterpreter.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => intraprocedural}/IntraproceduralVirtualMethodCallInterpreter.scala (95%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 5ea681549d..69fb0d6a69 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -37,7 +37,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathEleme import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.ExprStmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index e9d14a7b03..7d51aa8aaa 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -28,7 +28,7 @@ import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala similarity index 91% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala index ba4e2dda8c..cfa002583f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -12,6 +12,8 @@ import org.opalj.tac.BinaryExpr import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `BinaryExprInterpreter` is responsible for processing [[BinaryExpr]]ions. A list of currently diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala similarity index 89% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala index cb80bc1b67..a2018931e1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/DoubleValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -12,6 +12,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.DoubleConst import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `DoubleValueInterpreter` is responsible for processing [[DoubleConst]]s. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala similarity index 89% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala index 8b3a3e63d9..2c64ec9196 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/FloatValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -12,6 +12,8 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.FloatConst +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `FloatValueInterpreter` is responsible for processing [[FloatConst]]s. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala similarity index 89% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala index 4e1cb3ac7b..9cc6f7fdc5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntegerValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -12,6 +12,8 @@ import org.opalj.tac.IntConst import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `IntegerValueInterpreter` is responsible for processing [[IntConst]]s. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala similarity index 88% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala index 40f9ce7146..422af2854c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NewInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -9,6 +9,8 @@ import org.opalj.tac.New import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `NewInterpreter` is responsible for processing [[New]] expressions. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala similarity index 90% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala index 1142359fb9..45ec16a1a5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/StringConstInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -12,6 +12,8 @@ import org.opalj.tac.Stmt import org.opalj.tac.StringConst import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `StringConstInterpreter` is responsible for processing [[StringConst]]s. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala similarity index 99% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala index 87ded50a37..74e26f97e4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer import scala.collection.mutable.ListBuffer diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala similarity index 98% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala index 78d11b08e4..7030b2433b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.NonVirtualMethodCall diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 1fbc49a119..ef9163be41 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import scala.collection.mutable.ListBuffer @@ -15,6 +15,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `ArrayPreparationInterpreter` is responsible for preparing [[ArrayLoad]] as well as diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala similarity index 94% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index ddd528baa3..b8aae85e9b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -10,6 +10,7 @@ import org.opalj.tac.GetField import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `InterproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala similarity index 85% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala index 3a5c520a64..e0fd3216f8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -9,6 +9,8 @@ import org.opalj.tac.GetStatic import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler /** * The `InterproceduralGetStaticInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala similarity index 87% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index b66b4d2892..16331aab64 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult @@ -28,6 +28,15 @@ import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.DoubleConst import org.opalj.tac.FloatConst +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.FloatValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.IntegerValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer.ArrayFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer.NonVirtualMethodCallFinalizer /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -35,7 +44,8 @@ import org.opalj.tac.FloatConst * expressions usually come from the definitions sites of the variable of interest. *

    * For this interpretation handler used interpreters (concrete instances of - * [[AbstractStringInterpreter]]) can either return a final or intermediate result. + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter]]) can + * either return a final or intermediate result. * * @param cfg The control flow graph that underlies the program / method in which the expressions of * interest reside. @@ -157,7 +167,7 @@ class InterproceduralInterpretationHandler( object InterproceduralInterpretationHandler { /** - * @see [[IntraproceduralInterpretationHandler]] + * @see [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] */ def apply( cfg: CFG[Stmt[V], TACStmts[V]], diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 260a815f50..fc3a44fc13 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import scala.collection.mutable.ListBuffer @@ -18,6 +18,7 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `InterproceduralNonVirtualFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index ac5253ef75..bf6174f7db 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult @@ -14,6 +14,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `InterproceduralNonVirtualMethodCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index d3487f8c41..448ac83701 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import scala.collection.mutable.ListBuffer @@ -19,6 +19,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala similarity index 98% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala index f115b36d09..460528e851 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -16,6 +16,8 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala index 4c6747d34f..ba6f5eca3c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -13,6 +13,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `InterproceduralVirtualMethodCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala similarity index 96% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala index 947d7069ce..ceffca3516 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import scala.collection.mutable.ListBuffer @@ -14,6 +14,7 @@ import org.opalj.tac.Assignment import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralArrayInterpreter` is responsible for processing [[ArrayLoad]] as well as diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala similarity index 93% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala index f23c717c8e..8f4428cae3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -9,6 +9,7 @@ import org.opalj.tac.GetField import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala similarity index 93% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala index 3fe64272ef..360ba16cae 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -9,6 +9,7 @@ import org.opalj.tac.GetStatic import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralGetStaticInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala similarity index 87% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala index 5fd17bddfc..4f3360d990 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -24,6 +24,13 @@ import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.DoubleConst import org.opalj.tac.FloatConst +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryExprInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.FloatValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.IntegerValueInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter /** * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are @@ -31,7 +38,8 @@ import org.opalj.tac.FloatConst * expressions usually come from the definitions sites of the variable of interest. *

    * For this interpretation handler it is crucial that all used interpreters (concrete instances of - * [[AbstractStringInterpreter]]) return a final computation result! + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter]]) return + * a final computation result! * * @param cfg The control flow graph that underlies the program / method in which the expressions of * interest reside. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala index c7c9ea0a15..e305babdc4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -9,6 +9,7 @@ import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralNonVirtualFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala similarity index 96% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala index fb16e24311..c1d8e48d3f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import scala.collection.mutable.ListBuffer @@ -12,6 +12,7 @@ import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralNonVirtualMethodCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala similarity index 92% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala index 4f95bab678..07e4569016 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -9,6 +9,7 @@ import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralStaticFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala index a727952bae..68e36dd78b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -18,6 +18,8 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler /** * The `IntraproceduralVirtualFunctionCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala similarity index 95% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala index 69bed8020a..fc2ee042de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/IntraproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result @@ -12,6 +12,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter /** * The `IntraproceduralVirtualMethodCallInterpreter` is responsible for processing diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 36c2f49599..0ac85eafc3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -104,16 +104,16 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { * @param fpe2Sci A mapping from [[FlatPathElement.element]] values to * [[StringConstancyInformation]]. Make use of this mapping if some * StringConstancyInformation need to be used that the - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler]] + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] * cannot infer / derive. For instance, if the exact value of an * expression needs to be determined by calling the * [[org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis]] * on another instance, store this information in fpe2Sci. * @param resetExprHandler Whether to reset the underlying - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler]] + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] * or not. When calling this function from outside, the default value * should do fine in most of the cases. For further information, see - * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.IntraproceduralInterpretationHandler.reset]]. + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler.reset]]. * * @return If an empty [[Path]] is given, `None` will be returned. Otherwise, the transformed * [[org.opalj.br.fpcf.properties.properties.StringTree]] will be returned. Note that From 2fef579cc0daae2b0585b8bf4fd5b39c0aaf6d69 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 13:15:07 +0100 Subject: [PATCH 165/316] Refactored code to avoid code duplication. --- .../finalizer/ArrayFinalizer.scala | 32 +-------- .../ArrayPreparationInterpreter.scala | 70 ++++++++++++------- 2 files changed, 49 insertions(+), 53 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala index 74e26f97e4..8a4191bb60 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala @@ -1,17 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer -import scala.collection.mutable.ListBuffer - import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ArrayLoad -import org.opalj.tac.ArrayStore -import org.opalj.tac.Assignment import org.opalj.tac.Stmt import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter /** * @author Patrick Mell @@ -23,32 +20,9 @@ class ArrayFinalizer(cfg: CFG[Stmt[V], TACStmts[V]], state: ComputationState) { def interpret( instr: T, defSite: Int ): Unit = { - val stmts = cfg.code.instructions - val allDefSites = ListBuffer[Int]() - val defSites = instr.arrayRef.asVar.definedBy.toArray - defSites.filter(_ >= 0).sorted.foreach { next ⇒ - val arrDecl = stmts(next) - val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted - // Process ArrayStores - sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int ⇒ - allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) - } - // Process ArrayLoads - sortedArrDeclUses.filter { - stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) ⇒ true - case _ ⇒ false - } - } foreach { f: Int ⇒ - val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - allDefSites.appendAll(defs.toArray) - } - } - + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, cfg) state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple( - allDefSites.sorted.map { state.fpe2sci(_) }.toList + allDefSites.sorted.map { state.fpe2sci(_) } ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index ef9163be41..47a2e575af 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -50,34 +50,12 @@ class ArrayPreparationInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val stmts = cfg.code.instructions val results = ListBuffer[ProperPropertyComputationResult]() - // Loop over all possible array values - val allDefSites = ListBuffer[Int]() val defSites = instr.arrayRef.asVar.definedBy.toArray - defSites.filter(_ >= 0).sorted.foreach { next ⇒ - val arrDecl = stmts(next) - val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted - // Process ArrayStores - sortedArrDeclUses.filter { - stmts(_).isInstanceOf[ArrayStore[V]] - } foreach { f: Int ⇒ - allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) - } - // Process ArrayLoads - sortedArrDeclUses.filter { - stmts(_) match { - case Assignment(_, _, _: ArrayLoad[V]) ⇒ true - case _ ⇒ false - } - } foreach { f: Int ⇒ - val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy - allDefSites.appendAll(defs.toArray) - } - } + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, cfg) - allDefSites.sorted.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { + allDefSites.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, r: Result) ⇒ state.appendResultToFpe2Sci(ds, r) results.append(r) @@ -110,3 +88,47 @@ class ArrayPreparationInterpreter( } } + +object ArrayPreparationInterpreter { + + type T = ArrayLoad[V] + + /** + * This function retrieves all definition sites of the array stores and array loads that belong + * to the given instruction. + * + * @param instr The [[ArrayLoad]] instruction to get the definition sites for. + * @param cfg The underlying control flow graph. + * @return Returns all definition sites associated with the array stores and array loads of the + * given instruction. The result list is sorted in ascending order. + */ + def getStoreAndLoadDefSites(instr: T, cfg: CFG[Stmt[V], TACStmts[V]]): List[Int] = { + val stmts = cfg.code.instructions + val allDefSites = ListBuffer[Int]() + val defSites = instr.arrayRef.asVar.definedBy.toArray + + defSites.filter(_ >= 0).sorted.foreach { next ⇒ + val arrDecl = stmts(next) + val sortedArrDeclUses = arrDecl.asAssignment.targetVar.usedBy.toArray.sorted + // For ArrayStores + sortedArrDeclUses.filter { + stmts(_).isInstanceOf[ArrayStore[V]] + } foreach { f: Int ⇒ + allDefSites.appendAll(stmts(f).asArrayStore.value.asVar.definedBy.toArray) + } + // For ArrayLoads + sortedArrDeclUses.filter { + stmts(_) match { + case Assignment(_, _, _: ArrayLoad[V]) ⇒ true + case _ ⇒ false + } + } foreach { f: Int ⇒ + val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy + allDefSites.appendAll(defs.toArray) + } + } + + allDefSites.sorted.toList + } + +} From cc47d2d21c6c0161ec53023cb6336403dd3141c0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 13:35:18 +0100 Subject: [PATCH 166/316] Created a common interface for the finalizers. --- .../finalizer/AbstractFinalizer.scala | 35 +++++++++++++++++++ .../finalizer/ArrayFinalizer.scala | 15 +++++--- .../NonVirtualMethodCallFinalizer.scala | 13 ++++--- ...InterproceduralInterpretationHandler.scala | 4 +-- 4 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala new file mode 100644 index 0000000000..dd970a5dd7 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer + +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState + +/** + * When processing instruction interprocedurally, it is not always possible to compute a final + * result for an instruction. For example, consider the `append` operation of a StringBuilder where + * the `append` argument is a call to another function. This function result is likely to be not + * ready right away, which is why a final result for that `append` operation cannot yet be computed. + *

    + * Implementations of this class finalize the result for instructions. For instance, for `append`, + * a finalizer would use all partial results (receiver and `append` value) to compute the final + * result. However, '''this assumes that all partial results are available when finalizing a + * result!''' + * + * @param state The computation state to use to retrieve partial results and to write the final + * result back. + */ +abstract class AbstractFinalizer(state: ComputationState) { + + protected type T <: Any + + /** + * Implementations of this class finalize an instruction of type [[T]] which they are supposed + * to override / refine. This function does not return any result, however, the final result + * computed in this function is to be set in [[state.fpe2sci]] at position `defSite` by concrete + * implementations. + * + * @param instr The instruction that is to be finalized. + * @param defSite The definition site that corresponds to the given instruction. + */ + def finalizeInterpretation(instr: T, defSite: Int): Unit + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala index 8a4191bb60..407e408124 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala @@ -13,13 +13,18 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedura /** * @author Patrick Mell */ -class ArrayFinalizer(cfg: CFG[Stmt[V], TACStmts[V]], state: ComputationState) { +class ArrayFinalizer( + state: ComputationState, cfg: CFG[Stmt[V], TACStmts[V]] +) extends AbstractFinalizer(state) { - type T = ArrayLoad[V] + override type T = ArrayLoad[V] - def interpret( - instr: T, defSite: Int - ): Unit = { + /** + * Finalizes [[ArrayLoad]]s. + *

    + * @inheritdoc + */ + override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, cfg) state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple( allDefSites.sorted.map { state.fpe2sci(_) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala index 7030b2433b..e0c22638db 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala @@ -9,13 +9,16 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V /** * @author Patrick Mell */ -class NonVirtualMethodCallFinalizer(state: ComputationState) { +class NonVirtualMethodCallFinalizer(state: ComputationState) extends AbstractFinalizer(state) { - type T = NonVirtualMethodCall[V] + override type T = NonVirtualMethodCall[V] - def interpret( - instr: T, defSite: Int - ): Unit = { + /** + * Finalizes [[NonVirtualMethodCall]]s. + *

    + * @inheritdoc + */ + override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple(scis.toList) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 16331aab64..0c88a62306 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -155,9 +155,9 @@ class InterproceduralInterpretationHandler( ): Unit = { stmts(defSite) match { case nvmc: NonVirtualMethodCall[V] ⇒ - new NonVirtualMethodCallFinalizer(state).interpret(nvmc, defSite) + new NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new ArrayFinalizer(cfg, state).interpret(expr, defSite) + new ArrayFinalizer(state, cfg).finalizeInterpretation(expr, defSite) case _ ⇒ } } From b793bc7695e202bee9dbf14e6c2c1bf75330b778 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 13:37:24 +0100 Subject: [PATCH 167/316] Moved the "finalizer" package into the "interprocedural" package (as finalizers are only relevant for interprocedural processing). --- .../InterproceduralInterpretationHandler.scala | 4 ++-- .../{ => interprocedural}/finalizer/AbstractFinalizer.scala | 2 +- .../{ => interprocedural}/finalizer/ArrayFinalizer.scala | 2 +- .../finalizer/NonVirtualMethodCallFinalizer.scala | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/finalizer/AbstractFinalizer.scala (98%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/finalizer/ArrayFinalizer.scala (97%) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/{ => interprocedural}/finalizer/NonVirtualMethodCallFinalizer.scala (96%) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 0c88a62306..0e149094f6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -32,11 +32,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryE import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.FloatValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.IntegerValueInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer.ArrayFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.ArrayFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer.NonVirtualMethodCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala similarity index 98% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala index dd970a5dd7..35b930faef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/AbstractFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala index 407e408124..9850fe4ca3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala similarity index 96% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala index e0c22638db..4e497f30e5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala @@ -1,5 +1,5 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.finalizer +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.NonVirtualMethodCall From 59478e975961f9b6bc30347263a7c1f270fa2ca9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Feb 2019 17:32:00 +0100 Subject: [PATCH 168/316] Interprocedural "append" operations are now supported. --- .../InterproceduralTestMethods.java | 64 +++++++- .../InterproceduralStringAnalysis.scala | 25 ++- ...InterproceduralInterpretationHandler.scala | 17 +- ...lFunctionCallPreparationInterpreter.scala} | 148 +++++++++++------- .../VirtualFunctionCallFinalizer.scala | 65 ++++++++ 5 files changed, 248 insertions(+), 71 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/{InterproceduralVirtualFunctionCallInterpreter.scala => VirtualFunctionCallPreparationInterpreter.scala} (59%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 723a8305c3..c668d634a3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -8,8 +8,7 @@ import java.io.FileNotFoundException; import java.util.Scanner; -import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.CONSTANT; -import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.DYNAMIC; +import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; /** * This file contains various tests for the InterproceduralStringAnalysis. For further information @@ -135,6 +134,67 @@ public void arrayTest(int i) { analyzeString(classes[i]); } + @StringDefinitionsCollection( + value = "a case that tests that the append interpretation of only intraprocedural " + + "expressions still works", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "value:(A|BC)Z" + ) + + }) + public void appendTest0(int i) { + StringBuilder sb = new StringBuilder("value:"); + if (i % 2 == 0) { + sb.append('A'); + } else { + sb.append("BC"); + } + sb.append('Z'); + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "a case where function calls are involved in append operations", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "classname:StringBuilder,osname:\\w" + ) + + }) + public void appendTest1() { + StringBuilder sb = new StringBuilder("classname:"); + sb.append(getSimpleStringBuilderClassName()); + sb.append(",osname:"); + sb.append(System.getProperty("os.name:")); + analyzeString(sb.toString()); + } + + @StringDefinitionsCollection( + value = "a case where function calls are involved in append operations", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(java.lang.Runtime|java.lang.StringBuilder|" + + "ERROR!) - Done" + ) + + }) + public void appendTest2(int classToLoad) { + StringBuilder sb; + if (classToLoad == 0) { + sb = new StringBuilder(getRuntimeClassName()); + } else if (classToLoad == 1) { + sb = new StringBuilder(getStringBuilderClassName()); + } else { + sb = new StringBuilder("ERROR!"); + } + sb.append(" - Done"); + analyzeString(sb.toString()); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 69fb0d6a69..29e829ff3e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -69,11 +69,24 @@ case class ComputationState( var params: List[StringConstancyInformation] = List() /** - * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly. + * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, + * however, only if `defSite` is not yet present. */ def appendResultToFpe2Sci(defSite: Int, r: Result): Unit = { - val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation - fpe2sci(defSite) = sci + if (!fpe2sci.contains(defSite)) { + val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + fpe2sci(defSite) = sci + } + } + + /** + * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] + * map accordingly, however, only if `defSite` is not yet present. + */ + def appendResultToFpe2Sci(defSite: Int, sci: StringConstancyInformation): Unit = { + if (!fpe2sci.contains(defSite)) { + fpe2sci(defSite) = sci + } } /** @@ -371,10 +384,8 @@ class InterproceduralStringAnalysis( case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { iHandler.processDefSite(index, state.params) match { - case Result(r) ⇒ - val p = r.p.asInstanceOf[StringConstancyProperty] - state.fpe2sci(index) = p.stringConstancyInformation - case _ ⇒ hasFinalResult = false + case r: Result ⇒ state.appendResultToFpe2Sci(index, r) + case _ ⇒ hasFinalResult = false } } case npe: NestedPathElement ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 0e149094f6..9d74340f58 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -37,6 +37,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.VirtualFunctionCallFinalizer /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -108,8 +109,8 @@ class InterproceduralInterpretationHandler( state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) result case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - new InterproceduralVirtualFunctionCallInterpreter( - cfg, this, callees, params + new VirtualFunctionCallPreparationInterpreter( + cfg, this, state, params ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( @@ -126,8 +127,8 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: GetField[V]) ⇒ new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ - new InterproceduralVirtualFunctionCallInterpreter( - cfg, this, callees, params + new VirtualFunctionCallPreparationInterpreter( + cfg, this, state, params ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( @@ -156,8 +157,12 @@ class InterproceduralInterpretationHandler( stmts(defSite) match { case nvmc: NonVirtualMethodCall[V] ⇒ new NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) - case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new ArrayFinalizer(state, cfg).finalizeInterpretation(expr, defSite) + case Assignment(_, _, al: ArrayLoad[V]) ⇒ + new ArrayFinalizer(state, cfg).finalizeInterpretation(al, defSite) + case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ + new VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) + case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ + new VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) case _ ⇒ } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala similarity index 59% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 460528e851..ff3aa02852 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -7,7 +7,6 @@ import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.ObjectType -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -18,6 +17,7 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState /** * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing @@ -28,10 +28,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation * * @author Patrick Mell */ -class InterproceduralVirtualFunctionCallInterpreter( +class VirtualFunctionCallPreparationInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, - callees: Callees, + state: ComputationState, params: List[StringConstancyInformation] ) extends AbstractStringInterpreter(cfg, exprHandler) { @@ -49,7 +49,7 @@ class InterproceduralVirtualFunctionCallInterpreter( *

  • * `replace`: Calls to the `replace` function of [[StringBuilder]] and [[StringBuffer]]. For * further information how this operation is processed, see - * [[InterproceduralVirtualFunctionCallInterpreter.interpretReplaceCall]]. + * [[VirtualFunctionCallPreparationInterpreter.interpretReplaceCall]]. *
  • *
  • * Apart from these supported methods, a list with [[StringConstancyProperty.lb]] @@ -57,16 +57,19 @@ class InterproceduralVirtualFunctionCallInterpreter( *
  • * * - * If none of the above-described cases match, an empty list will be returned. + * If none of the above-described cases match, a final result containing + * [[StringConstancyProperty.getNeutralElement]] is returned. * * @note For this implementation, `defSite` plays a role! * + * @note This function takes care of updating [[state.fpe2sci]] as necessary. + * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val e: Integer = defSite - instr.name match { - case "append" ⇒ interpretAppendCall(instr) + val result = instr.name match { + case "append" ⇒ interpretAppendCall(instr, defSite) case "toString" ⇒ interpretToStringCall(instr) case "replace" ⇒ interpretReplaceCall(instr) case _ ⇒ @@ -77,6 +80,12 @@ class InterproceduralVirtualFunctionCallInterpreter( Result(e, StringConstancyProperty.getNeutralElement) } } + + result match { + case r: Result ⇒ state.appendResultToFpe2Sci(defSite, r) + case _ ⇒ + } + result } /** @@ -85,26 +94,42 @@ class InterproceduralVirtualFunctionCallInterpreter( * the expected behavior cannot be guaranteed. */ private def interpretAppendCall( - appendCall: VirtualFunctionCall[V] + appendCall: VirtualFunctionCall[V], defSite: Int ): ProperPropertyComputationResult = { - val receiverSci = receiverValuesOfAppendCall(appendCall).stringConstancyInformation - val appendSci = valueOfAppendCall(appendCall).stringConstancyInformation + val receiverResults = receiverValuesOfAppendCall(appendCall, state) + val appendResult = valueOfAppendCall(appendCall, state) + + // If there is an intermediate result, return this one (then the final result cannot yet be + // computed) + if (!receiverResults.head.isInstanceOf[Result]) { + return receiverResults.head + } else if (!appendResult.isInstanceOf[Result]) { + return appendResult + } + + val receiverScis = receiverResults.map { + StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation + } + val appendSci = + StringConstancyProperty.extractFromPPCR(appendResult).stringConstancyInformation // The case can occur that receiver and append value are empty; although, it is // counter-intuitive, this case may occur if both, the receiver and the parameter, have been // processed before - val sci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { + val areAllReceiversNeutral = receiverScis.forall(_.isTheNeutralElement) + val finalSci = if (areAllReceiversNeutral && appendSci.isTheNeutralElement) { StringConstancyInformation.getNeutralElement } // It might be that we have to go back as much as to a New expression. As they do not // produce a result (= empty list), the if part - else if (receiverSci.isTheNeutralElement) { + else if (areAllReceiversNeutral) { appendSci } // The append value might be empty, if the site has already been processed (then this // information will come from another StringConstancyInformation object else if (appendSci.isTheNeutralElement) { - receiverSci + StringConstancyInformation.reduceMultiple(receiverScis) } // Receiver and parameter information are available => Combine them else { + val receiverSci = StringConstancyInformation.reduceMultiple(receiverScis) StringConstancyInformation( StringConstancyLevel.determineForConcat( receiverSci.constancyLevel, appendSci.constancyLevel @@ -114,27 +139,39 @@ class InterproceduralVirtualFunctionCallInterpreter( ) } - Result(appendCall, StringConstancyProperty(sci)) + state.appendResultToFpe2Sci(defSite, finalSci) + val e: Integer = defSite + Result(e, StringConstancyProperty(finalSci)) } /** - * This function determines the current value of the receiver object of an `append` call. + * This function determines the current value of the receiver object of an `append` call. For + * the result list, there is the following convention: A list with one element of type + * [[org.opalj.fpcf.InterimResult]] indicates that a final result for the receiver value could + * not be computed. Otherwise, the result list will contain >= 1 elements of type [[Result]] + * indicating that all final results for the receiver value are available. + * + * @note All final results computed by this function are put int [[state.fpe2sci]] even if the + * returned list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V] - ): StringConstancyProperty = { - // There might be several receivers, thus the map; from the processed sites, however, use - // only the head as a single receiver interpretation will produce one element + call: VirtualFunctionCall[V], state: ComputationState + ): List[ProperPropertyComputationResult] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted - val scis = defSites.map(exprHandler.processDefSite(_, params)).map( - _.asInstanceOf[Result].finalEP.p.asInstanceOf[StringConstancyProperty] - ).filter { - !_.stringConstancyInformation.isTheNeutralElement - } - if (scis.isEmpty) { - StringConstancyProperty.getNeutralElement + + val allResults = defSites.map(ds ⇒ (ds, exprHandler.processDefSite(ds, params))) + val finalResults = allResults.filter(_._2.isInstanceOf[Result]) + val intermediateResults = allResults.filter(!_._2.isInstanceOf[Result]) + + // Extend the state by the final results + finalResults.foreach { next ⇒ + state.appendResultToFpe2Sci(next._1, next._2.asInstanceOf[Result]) + } + + if (allResults.length == finalResults.length) { + finalResults.map(_._2).toList } else { - scis.head + List(intermediateResults.head._2) } } @@ -143,56 +180,54 @@ class InterproceduralVirtualFunctionCallInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V] - ): StringConstancyProperty = { + call: VirtualFunctionCall[V], state: ComputationState + ): ProperPropertyComputationResult = { val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append val defSiteHead = param.definedBy.head - var value = StringConstancyProperty.extractFromPPCR( - exprHandler.processDefSite(defSiteHead, params) - ) - // If defSiteHead points to a New, value will be the empty list. In that case, process + var value = exprHandler.processDefSite(defSiteHead, params) + + // Defer the computation if there is no final result (yet) + if (!value.isInstanceOf[Result]) { + return value + } + + var valueSci = StringConstancyProperty.extractFromPPCR(value).stringConstancyInformation + // If defSiteHead points to a "New", value will be the empty list. In that case, process // the first use site (which is the call) - if (value.isTheNeutralElement) { - value = StringConstancyProperty.extractFromPPCR(exprHandler.processDefSite( - cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min, params - )) + if (valueSci.isTheNeutralElement) { + val ds = cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min + value = exprHandler.processDefSite(ds, params) + // Again, defer the computation if there is no final result (yet) + if (!value.isInstanceOf[Result]) { + return value + } } - val sci = value.stringConstancyInformation + valueSci = StringConstancyProperty.extractFromPPCR(value).stringConstancyInformation val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ // The value was already computed above; however, we need to check whether the // append takes an int value or a char (if it is a constant char, convert it) if (call.descriptor.parameterType(0).isCharType && - sci.constancyLevel == StringConstancyLevel.CONSTANT) { - sci.copy( - possibleStrings = sci.possibleStrings.toInt.toChar.toString + valueSci.constancyLevel == StringConstancyLevel.CONSTANT) { + valueSci.copy( + possibleStrings = valueSci.possibleStrings.toInt.toChar.toString ) } else { - sci + valueSci } case ComputationalTypeFloat ⇒ InterpretationHandler.getConstancyInfoForDynamicFloat // Otherwise, try to compute case _ ⇒ - // It might be necessary to merge the values of the receiver and of the parameter - // value.size match { - // case 0 ⇒ None - // case 1 ⇒ Some(value.head) - // case _ ⇒ Some(StringConstancyInformation( - // StringConstancyLevel.determineForConcat( - // value.head.constancyLevel, value(1).constancyLevel - // ), - // StringConstancyType.APPEND, - // value.head.possibleStrings + value(1).possibleStrings - // )) - // } - sci + valueSci } - StringConstancyProperty(finalSci) + state.appendResultToFpe2Sci(defSiteHead, valueSci) + val e: Integer = defSiteHead + Result(e, StringConstancyProperty(finalSci)) } /** @@ -203,6 +238,7 @@ class InterproceduralVirtualFunctionCallInterpreter( private def interpretToStringCall( call: VirtualFunctionCall[V] ): ProperPropertyComputationResult = + // TODO: Can it produce an intermediate result??? exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala new file mode 100644 index 0000000000..0e325514f9 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -0,0 +1,65 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer + +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel +import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.VirtualFunctionCall + +/** + * @author Patrick Mell + */ +class VirtualFunctionCallFinalizer( + state: ComputationState, cfg: CFG[Stmt[V], TACStmts[V]] +) extends AbstractFinalizer(state) { + + override type T = VirtualFunctionCall[V] + + /** + * Finalizes [[VirtualFunctionCall]]s. Currently, this finalizer supports only the "append" + * function. + *

    + * @inheritdoc + */ + override def finalizeInterpretation(instr: T, defSite: Int): Unit = { + if (instr.name == "append") { + finalizeAppend(instr, defSite) + } + } + + /** + * This function actually finalizes append calls by mimicking the behavior of the corresponding + * interpretation function of + * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.VirtualFunctionCallPreparationInterpreter]]. + */ + private def finalizeAppend(instr: T, defSite: Int): Unit = { + val receiverSci = StringConstancyInformation.reduceMultiple( + instr.receiver.asVar.definedBy.toArray.sorted.map(state.fpe2sci(_)).toList + ) + val appendSci = state.fpe2sci(instr.params.head.asVar.definedBy.head) + + val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { + receiverSci + } else if (receiverSci.isTheNeutralElement) { + appendSci + } else if (appendSci.isTheNeutralElement) { + receiverSci + } else { + StringConstancyInformation( + StringConstancyLevel.determineForConcat( + receiverSci.constancyLevel, appendSci.constancyLevel + ), + StringConstancyType.APPEND, + receiverSci.possibleStrings + appendSci.possibleStrings + ) + } + + state.fpe2sci(defSite) = finalSci + } + +} From 52494602ceccda6a8f935e7ca95f8f960f2a1ec5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Feb 2019 09:11:17 +0100 Subject: [PATCH 169/316] Removed a comment (TODO). --- .../InterproceduralNonVirtualMethodCallInterpreter.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index bf6174f7db..b4b8af3906 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -94,8 +94,6 @@ class InterproceduralNonVirtualMethodCallInterpreter( state.appendResultToFpe2Sci(ds, r) case _ ⇒ } - // TODO: is it enough to return only one (the first) IntermediateResult in case - // there are more? (The others were registered already, anyway.) returnIR } } From df988a5942247d1908cc3b9d41a97b6a79f58036 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Feb 2019 09:13:55 +0100 Subject: [PATCH 170/316] Refined the "getDeclaredMethod" to include the declaring class in the search for a method as well. --- .../AbstractStringInterpreter.scala | 16 +++++++++++----- ...eduralNonVirtualFunctionCallInterpreter.scala | 2 +- ...proceduralStaticFunctionCallInterpreter.scala | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 9c4e6bc18e..a3c89e1a49 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -9,6 +9,7 @@ import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.DefinedMethod +import org.opalj.br.ReferenceType import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -63,14 +64,19 @@ abstract class AbstractStringInterpreter( } /** - * Takes `declaredMethods` as well as a method `name`, extracts the method with the given `name` - * from `declaredMethods` and returns this one as a [[Method]]. It might be, that the given - * method cannot be found. In these cases, `None` will be returned. + * Takes `declaredMethods`, a `declaringClass`, and a method `name` and extracts the method with + * the given `name` from `declaredMethods` where the declaring classes match. The found entry is + * then returned as a [[Method]] instance. + *

    + * It might be, that the given method cannot be found (e.g., when it is not in the scope of the + * current project). In these cases, `None` will be returned. */ protected def getDeclaredMethod( - declaredMethods: DeclaredMethods, name: String + declaredMethods: DeclaredMethods, declaringClass: ReferenceType, methodName: String ): Option[Method] = { - val dm = declaredMethods.declaredMethods.find(_.name == name) + val dm = declaredMethods.declaredMethods.find { dm ⇒ + dm.name == methodName && dm.declaringClassType == declaringClass + } if (dm.isDefined && dm.get.isInstanceOf[DefinedMethod]) { Some(dm.get.definedMethod) } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index fc3a44fc13..df0c3c67ed 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -51,7 +51,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val methodOption = getDeclaredMethod(declaredMethods, instr.name) + val methodOption = getDeclaredMethod(declaredMethods, instr.declaringClass, instr.name) if (methodOption.isEmpty) { val e: Integer = defSite diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 448ac83701..e95ac0e33b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -53,7 +53,7 @@ class InterproceduralStaticFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val methodOption = getDeclaredMethod(declaredMethods, instr.name) + val methodOption = getDeclaredMethod(declaredMethods, instr.declaringClass, instr.name) if (methodOption.isEmpty) { val e: Integer = defSite From 99664c7f2213d3f296a13be70107ac2aa5be4ec6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Feb 2019 14:18:49 +0100 Subject: [PATCH 171/316] Added support for virtual function calls where multiple implementations might be present. Corresponding test cases were added. --- .../InterproceduralTestMethods.java | 29 +++ .../hierarchies/GreetingService.java | 9 + .../hierarchies/HelloGreeting.java | 11 + .../hierarchies/SimpleHelloGreeting.java | 11 + .../org/opalj/fpcf/StringAnalysisTest.scala | 5 +- .../StringConstancyInformation.scala | 4 +- .../InterproceduralStringAnalysis.scala | 206 +++++++++--------- .../IntraproceduralStringAnalysis.scala | 2 +- .../AbstractStringInterpreter.scala | 32 ++- .../ArrayPreparationInterpreter.scala | 2 +- ...InterproceduralInterpretationHandler.scala | 4 +- ...ralNonVirtualFunctionCallInterpreter.scala | 10 +- ...duralNonVirtualMethodCallInterpreter.scala | 2 +- ...ceduralStaticFunctionCallInterpreter.scala | 14 +- ...alFunctionCallPreparationInterpreter.scala | 98 ++++++++- .../finalizer/ArrayFinalizer.scala | 8 +- .../NonVirtualMethodCallFinalizer.scala | 6 +- .../VirtualFunctionCallFinalizer.scala | 8 +- .../preprocessing/PathTransformer.scala | 10 +- 19 files changed, 309 insertions(+), 162 deletions(-) create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/GreetingService.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/HelloGreeting.java create mode 100644 DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/SimpleHelloGreeting.java diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index c668d634a3..b1b67c18c6 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string_analysis; +import org.opalj.fpcf.fixtures.string_analysis.hierarchies.GreetingService; +import org.opalj.fpcf.fixtures.string_analysis.hierarchies.HelloGreeting; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; @@ -195,6 +197,33 @@ public void appendTest2(int classToLoad) { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "a case where the concrete instance of an interface is known", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello World" + ) + + }) + public void knownHierarchyInstanceTest() { + GreetingService gs = new HelloGreeting(); + analyzeString(gs.getGreeting("World")); + } + + @StringDefinitionsCollection( + value = "a case where the concrete instance of an interface is NOT known", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(Hello World|Hello)" + ) + + }) + public void unknownHierarchyInstanceTest(GreetingService greetingService) { + analyzeString(greetingService.getGreeting("World")); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/GreetingService.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/GreetingService.java new file mode 100644 index 0000000000..65ecf9b691 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/GreetingService.java @@ -0,0 +1,9 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis.hierarchies; + +public interface GreetingService { + + String getGreeting(String name); + +} + diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/HelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/HelloGreeting.java new file mode 100644 index 0000000000..ca9cb0c921 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/HelloGreeting.java @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis.hierarchies; + +public class HelloGreeting implements GreetingService { + + @Override + public String getGreeting(String name) { + return "Hello " + name; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/SimpleHelloGreeting.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/SimpleHelloGreeting.java new file mode 100644 index 0000000000..af86ec1dab --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/hierarchies/SimpleHelloGreeting.java @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.string_analysis.hierarchies; + +public class SimpleHelloGreeting implements GreetingService { + + @Override + public String getGreeting(String name) { + return "Hello"; + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 2ba3276b03..19da829a1f 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -255,7 +255,10 @@ object InterproceduralStringAnalysisTest { // Files to load for the runner val filesToLoad = List( "fixtures/string_analysis/InterproceduralTestMethods.class", - "fixtures/string_analysis/StringProvider.class" + "fixtures/string_analysis/StringProvider.class", + "fixtures/string_analysis/hierarchies/GreetingService.class", + "fixtures/string_analysis/hierarchies/HelloGreeting.class", + "fixtures/string_analysis/hierarchies/SimpleHelloGreeting.class" ) } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 4f7b18f2d1..4164d7756e 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -64,9 +64,9 @@ object StringConstancyInformation { * as described above; the empty list will throw an error! * @return Returns the reduced information in the fashion described above. */ - def reduceMultiple(scis: List[StringConstancyInformation]): StringConstancyInformation = { + def reduceMultiple(scis: Iterable[StringConstancyInformation]): StringConstancyInformation = { val relScis = scis.filter(!_.isTheNeutralElement) - relScis.length match { + relScis.size match { // The list may be empty, e.g., if the UVar passed to the analysis, refers to a // VirtualFunctionCall (they are not interpreted => an empty list is returned) => return // the neutral element diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 29e829ff3e..f5b1a0cd48 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -15,6 +15,7 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject import org.opalj.br.cfg.CFG @@ -24,8 +25,6 @@ import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.DeclaredMethod -import org.opalj.br.analyses.DeclaredMethods import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path @@ -48,22 +47,16 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath * have all required information ready for a final result. */ case class ComputationState( - var computedLeanPath: Option[Path], - cfg: CFG[Stmt[V], TACStmts[V]], - var callees: Option[Callees] = None + var computedLeanPath: Option[Path] = None, + var callees: Option[Callees] = None ) { + var cfg: CFG[Stmt[V], TACStmts[V]] = _ // If not empty, this very routine can only produce an intermediate result val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() // A mapping from DUVar elements to the corresponding indices of the FlatPathElements val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() // A mapping from values / indices of FlatPathElements to StringConstancyInformation - val fpe2sci: mutable.Map[Int, StringConstancyInformation] = mutable.Map() - // Some interpreters, such as the array interpreter, need preparation (see their comments for - // more information). This map stores the prepared data. The key of the outer map is the - // instruction that is to be interpreted. The key of the inner map is a definition site and the - // inner maps value the associated string constancy information - // val preparationSciInformation: mutable.Map[Any, mutable.Map[Int, StringConstancyInformation]] = - // mutable.Map() + val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() // Parameter values of method / function; a mapping from the definition sites of parameter ( // negative values) to a correct index of `params` has to be made! var params: List[StringConstancyInformation] = List() @@ -72,35 +65,27 @@ case class ComputationState( * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, * however, only if `defSite` is not yet present. */ - def appendResultToFpe2Sci(defSite: Int, r: Result): Unit = { - if (!fpe2sci.contains(defSite)) { - val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation - fpe2sci(defSite) = sci - } - } + def appendResultToFpe2Sci( + defSite: Int, r: Result, reset: Boolean = false + ): Unit = appendToFpe2Sci( + defSite, + StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation, + reset + ) /** * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] * map accordingly, however, only if `defSite` is not yet present. */ - def appendResultToFpe2Sci(defSite: Int, sci: StringConstancyInformation): Unit = { - if (!fpe2sci.contains(defSite)) { - fpe2sci(defSite) = sci + def appendToFpe2Sci( + defSite: Int, sci: StringConstancyInformation, reset: Boolean = false + ): Unit = { + if (reset || !fpe2sci.contains(defSite)) { + fpe2sci(defSite) = ListBuffer() } + fpe2sci(defSite).append(sci) } - /** - * addPreparationInformation is responsible for adding an entry to preparationSciInformation - */ - // def addPreparationInformation( - // instr: Any, defSite: Int, sci: StringConstancyInformation - // ): Unit = { - // if (!preparationSciInformation.contains(instr)) { - // preparationSciInformation(instr) = mutable.Map() - // } - // preparationSciInformation(instr)(defSite) = sci - // } - } /** @@ -134,39 +119,47 @@ class InterproceduralStringAnalysis( private var declaredMethods: DeclaredMethods = _ - // state will be set to a non-null value in "determinePossibleStrings" - var state: ComputationState = _ - def analyze(data: P): ProperPropertyComputationResult = { + val state = ComputationState() declaredMethods = project.get(DeclaredMethodsKey) // TODO: Is there a way to get the declared method in constant time? - val dm = declaredMethods.declaredMethods.find(dm ⇒ dm.name == data._2.name).get + val dm = declaredMethods.declaredMethods.find { dm ⇒ + dm.name == data._2.name && + dm.declaringClassType.toJava == data._2.classFile.thisType.toJava + }.get val calleesEOptP = ps(dm, Callees.key) if (calleesEOptP.hasUBP) { - determinePossibleStrings(data, calleesEOptP.ub) + state.callees = Some(calleesEOptP.ub) + determinePossibleStrings(data, state) } else { - val dependees = Iterable(calleesEOptP) + state.dependees(data) = ListBuffer() + state.dependees(data).append(calleesEOptP) + val dependees = state.dependees.values.flatten InterimResult( calleesEOptP, StringConstancyProperty.lb, StringConstancyProperty.ub, dependees, - calleesContinuation(calleesEOptP, dependees, data) + continuation(data, state) ) } } + /** + * Takes the `data` an analysis was started with as well as a computation `state` and determines + * the possible string values. This method returns either a final [[Result]] or an + * [[InterimResult]] depending on whether other information needs to be computed first. + */ private def determinePossibleStrings( - data: P, callees: Callees + data: P, state: ComputationState ): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) - val cfg = tacProvider(data._2).cfg - val stmts = cfg.code.instructions - state = ComputationState(None, cfg, Some(callees)) + state.cfg = tacProvider(data._2).cfg state.params = InterproceduralStringAnalysis.getParams(data) + val stmts = state.cfg.code.instructions val uvar = data._1 val defSites = uvar.definedBy.toArray.sorted @@ -175,7 +168,7 @@ class InterproceduralStringAnalysis( if (defSites.head < 0) { return Result(data, StringConstancyProperty.lb) } - val pathFinder: AbstractPathFinder = new WindowPathFinder(cfg) + val pathFinder: AbstractPathFinder = new WindowPathFinder(state.cfg) val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { @@ -207,7 +200,7 @@ class InterproceduralStringAnalysis( } } else { val iHandler = InterproceduralInterpretationHandler( - cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) + state.cfg, ps, declaredMethods, state, continuation(data, state) ) if (computeResultsForPath(state.computedLeanPath.get, iHandler, state)) { sci = new PathTransformer(iHandler).pathToStringTree( @@ -217,7 +210,7 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val leanPath = if (defSites.length == 1) { + state.computedLeanPath = Some(if (defSites.length == 1) { // Trivial case for just one element Path(List(FlatPathElement(defSites.head))) } else { @@ -228,58 +221,84 @@ class InterproceduralStringAnalysis( children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) } Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) - } + }) - val interHandler = InterproceduralInterpretationHandler( - cfg, ps, declaredMethods, state, continuation(data, callees, List(), state) + val iHandler = InterproceduralInterpretationHandler( + state.cfg, ps, declaredMethods, state, continuation(data, state) ) - state.computedLeanPath = Some(leanPath) - if (computeResultsForPath(leanPath, interHandler, state)) { - // All necessary information are available => Compute final result - return computeFinalResult(data, state, interHandler) + if (computeResultsForPath(state.computedLeanPath.get, iHandler, state)) { + sci = new PathTransformer(iHandler).pathToStringTree( + state.computedLeanPath.get, state.fpe2sci.toMap + ).reduce(true) } // No need to cover the else branch: interimResults.nonEmpty => dependees were added to // state.dependees, i.e., the if that checks whether state.dependees is non-empty will // always be true (thus, the value of "sci" does not matter) } - if (state.dependees.nonEmpty) { + if (state.dependees.values.nonEmpty) { InterimResult( data, StringConstancyProperty.ub, StringConstancyProperty.lb, state.dependees.values.flatten, - continuation(data, callees, state.dependees.values.flatten, state) + continuation(data, state) ) } else { Result(data, StringConstancyProperty(sci)) } } - private def calleesContinuation( - e: Entity, - dependees: Iterable[EOptionP[DeclaredMethod, Callees]], - inputData: P - )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case FinalP(callees: Callees) ⇒ - determinePossibleStrings(inputData, callees) - case InterimLUBP(lb, ub) ⇒ - InterimResult(e, lb, ub, dependees, calleesContinuation(e, dependees, inputData)) - case _ ⇒ throw new IllegalStateException("can occur?") + /** + * Continuation function for this analysis. + * + * @param state The current computation state. Within this continuation, dependees of the state + * might be updated. Furthermore, methods processing this continuation might alter + * the state. + * @param inputData The data which the string analysis was started with. + * @return Returns a final result if (already) available. Otherwise, an intermediate result will + * be returned. + */ + private def continuation( + inputData: P, + state: ComputationState + )(eps: SomeEPS): ProperPropertyComputationResult = { + eps.pk match { + case Callees.key ⇒ eps match { + case FinalP(callees: Callees) ⇒ + state.callees = Some(callees) + state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) + if (state.dependees(inputData).isEmpty) { + state.dependees.remove(inputData) + } + determinePossibleStrings(inputData, state) + case InterimLUBP(lb, ub) ⇒ InterimResult( + inputData, lb, ub, state.dependees.values.flatten, continuation(inputData, state) + ) + case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") + } + case StringConstancyProperty.key ⇒ + eps match { + case FinalP(p) ⇒ + processFinalP(inputData, state, eps.e, p) + case InterimLUBP(lb, ub) ⇒ InterimResult( + inputData, lb, ub, state.dependees.values.flatten, continuation(eps.e.asInstanceOf[P], state) + ) + case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") + } + } } private def finalizePreparations( path: Path, state: ComputationState, iHandler: InterproceduralInterpretationHandler - ): Unit = { - path.elements.foreach { - case FlatPathElement(index) ⇒ - if (!state.fpe2sci.contains(index)) { - iHandler.finalizeDefSite(index, state) - } - case npe: NestedPathElement ⇒ - finalizePreparations(Path(npe.element.toList), state, iHandler) - case _ ⇒ - } + ): Unit = path.elements.foreach { + case FlatPathElement(index) ⇒ + if (!state.fpe2sci.contains(index)) { + iHandler.finalizeDefSite(index, state) + } + case npe: NestedPathElement ⇒ + finalizePreparations(Path(npe.element.toList), state, iHandler) + case _ ⇒ } /** @@ -290,7 +309,7 @@ class InterproceduralStringAnalysis( * @param data The entity that was to analyze. * @param state The final computation state. For this state the following criteria must apply: * For each [[FlatPathElement]], there must be a corresponding entry in - * [[state.fpe2sci]]. If this criteria is not met, a [[NullPointerException]] will + * `state.fpe2sci`. If this criteria is not met, a [[NullPointerException]] will * be thrown (in this case there was some work to do left and this method should * not have been called)! * @return Returns the final result. @@ -310,8 +329,7 @@ class InterproceduralStringAnalysis( * [[org.opalj.fpcf.FinalP]]. */ private def processFinalP( - data: P, - // callees: Callees, + data: P, state: ComputationState, e: Entity, p: Property @@ -319,7 +337,7 @@ class InterproceduralStringAnalysis( // Add mapping information (which will be used for computing the final result) val retrievedProperty = p.asInstanceOf[StringConstancyProperty] val currentSci = retrievedProperty.stringConstancyInformation - state.fpe2sci.put(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) + state.appendToFpe2Sci(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) // No more dependees => Return the result for this analysis run state.dependees.foreach { case (k, v) ⇒ state.dependees(k) = v.filter(_.e != e) } @@ -327,7 +345,7 @@ class InterproceduralStringAnalysis( if (remDependees.isEmpty) { val iHandler = InterproceduralInterpretationHandler( state.cfg, ps, declaredMethods, state, - continuation(data, state.callees.get, List(), state) + continuation(data, state) ) computeFinalResult(data, state, iHandler) } else { @@ -336,33 +354,11 @@ class InterproceduralStringAnalysis( StringConstancyProperty.ub, StringConstancyProperty.lb, remDependees, - continuation(data, state.callees.get, remDependees, state) + continuation(data, state) ) } } - /** - * Continuation function. - * - * @param data The data that was passed to the `analyze` function. - * @param dependees A list of dependencies that this analysis run depends on. - * @param state The computation state (which was originally captured by `analyze` and possibly - * extended / updated by other methods involved in computing the final result. - * @return This function can either produce a final result or another intermediate result. - */ - private def continuation( - data: P, - callees: Callees, - dependees: Iterable[EOptionP[Entity, Property]], - state: ComputationState - )(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case FinalP(p) ⇒ processFinalP(data, state, eps.e, p) - case InterimLUBP(lb, ub) ⇒ InterimResult( - data, lb, ub, dependees, continuation(data, callees, dependees, state) - ) - case _ ⇒ throw new IllegalStateException("Could not process the continuation successfully.") - } - /** * This function traverses the given path, computes all string values along the path and stores * these information in the given state. @@ -384,7 +380,7 @@ class InterproceduralStringAnalysis( case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { iHandler.processDefSite(index, state.params) match { - case r: Result ⇒ state.appendResultToFpe2Sci(index, r) + case r: Result ⇒ state.appendResultToFpe2Sci(index, r, reset = true) case _ ⇒ hasFinalResult = false } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index 7d51aa8aaa..7469d9b451 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -185,7 +185,7 @@ class IntraproceduralStringAnalysis( if (remDependees.isEmpty) { val interpretationHandler = IntraproceduralInterpretationHandler(state.cfg) val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci.toMap + state.computedLeanPath, state.fpe2sci.map { case (k, v) ⇒ (k, ListBuffer(v)) }.toMap ).reduce(true) Result(data, StringConstancyProperty(finalSci)) } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index a3c89e1a49..b25f0d2e48 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -9,7 +9,7 @@ import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.DefinedMethod -import org.opalj.br.ReferenceType +import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -64,24 +64,22 @@ abstract class AbstractStringInterpreter( } /** - * Takes `declaredMethods`, a `declaringClass`, and a method `name` and extracts the method with - * the given `name` from `declaredMethods` where the declaring classes match. The found entry is - * then returned as a [[Method]] instance. - *

    - * It might be, that the given method cannot be found (e.g., when it is not in the scope of the - * current project). In these cases, `None` will be returned. + * This function returns all methods for a given `pc` among a set of `declaredMethods`. The + * second return value indicates whether at least one method has an unknown body (if `true`, + * then there is such a method). */ - protected def getDeclaredMethod( - declaredMethods: DeclaredMethods, declaringClass: ReferenceType, methodName: String - ): Option[Method] = { - val dm = declaredMethods.declaredMethods.find { dm ⇒ - dm.name == methodName && dm.declaringClassType == declaringClass - } - if (dm.isDefined && dm.get.isInstanceOf[DefinedMethod]) { - Some(dm.get.definedMethod) - } else { - None + protected def getMethodsForPC( + implicit + pc: Int, ps: PropertyStore, callees: Callees, declaredMethods: DeclaredMethods + ): (List[Method], Boolean) = { + var hasMethodWithUnknownBody = false + val methods = ListBuffer[Method]() + callees.callees(pc).foreach { + case definedMethod: DefinedMethod ⇒ methods.append(definedMethod.definedMethod) + case _ ⇒ hasMethodWithUnknownBody = true } + + (methods.sortBy(_.classFile.fqn).toList, hasMethodWithUnknownBody) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 47a2e575af..0588110017 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -57,7 +57,7 @@ class ArrayPreparationInterpreter( allDefSites.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, r: Result) ⇒ - state.appendResultToFpe2Sci(ds, r) + state.appendResultToFpe2Sci(ds, r, reset = true) results.append(r) case (_, ir: ProperPropertyComputationResult) ⇒ results.append(ir) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 9d74340f58..3fe3cba748 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -110,7 +110,7 @@ class InterproceduralInterpretationHandler( result case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ new VirtualFunctionCallPreparationInterpreter( - cfg, this, state, params + cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( @@ -128,7 +128,7 @@ class InterproceduralInterpretationHandler( new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ new VirtualFunctionCallPreparationInterpreter( - cfg, this, state, params + cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ new InterproceduralStaticFunctionCallInterpreter( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index df0c3c67ed..10c227c1d8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -51,14 +51,8 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val methodOption = getDeclaredMethod(declaredMethods, instr.declaringClass, instr.name) - - if (methodOption.isEmpty) { - val e: Integer = defSite - return Result(e, StringConstancyProperty.lb) - } - - val m = methodOption.get + val methods = getMethodsForPC(instr.pc, ps, state.callees.get, declaredMethods) + val m = methods._1.head val tac = getTACAI(ps, m, state) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index b4b8af3906..7d49c6fe0f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -91,7 +91,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( val returnIR = results.find(r ⇒ !r._2.isInstanceOf[Result]).get._2 results.foreach { case (ds, r: Result) ⇒ - state.appendResultToFpe2Sci(ds, r) + state.appendResultToFpe2Sci(ds, r, reset = true) case _ ⇒ } returnIR diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index e95ac0e33b..e6539db307 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -53,14 +53,18 @@ class InterproceduralStaticFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val methodOption = getDeclaredMethod(declaredMethods, instr.declaringClass, instr.name) + val methods, _ = getMethodsForPC( + instr.pc, ps, state.callees.get, declaredMethods + ) - if (methodOption.isEmpty) { - val e: Integer = defSite - return Result(e, StringConstancyProperty.lb) + // Static methods cannot be overwritten, thus 1) we do not need the second return value of + // getMethodsForPC and 2) interpreting the head is enough + if (methods._1.isEmpty) { + state.appendToFpe2Sci(defSite, StringConstancyProperty.lb.stringConstancyInformation) + return Result(instr, StringConstancyProperty.lb) } - val m = methodOption.get + val m = methods._1.head val tac = getTACAI(ps, m, state) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index ff3aa02852..e8365c92fc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -1,12 +1,18 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt import org.opalj.br.ObjectType +import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -18,6 +24,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis /** * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing @@ -29,10 +37,13 @@ import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState * @author Patrick Mell */ class VirtualFunctionCallPreparationInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - state: ComputationState, - params: List[StringConstancyInformation] + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + state: ComputationState, + declaredMethods: DeclaredMethods, + params: List[StringConstancyInformation], + c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] @@ -67,7 +78,6 @@ class VirtualFunctionCallPreparationInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val e: Integer = defSite val result = instr.name match { case "append" ⇒ interpretAppendCall(instr, defSite) case "toString" ⇒ interpretToStringCall(instr) @@ -75,8 +85,9 @@ class VirtualFunctionCallPreparationInterpreter( case _ ⇒ instr.descriptor.returnType match { case obj: ObjectType if obj.fqn == "java/lang/String" ⇒ - Result(e, StringConstancyProperty.lb) + interpretArbitraryCall(instr, defSite) case _ ⇒ + val e: Integer = defSite Result(e, StringConstancyProperty.getNeutralElement) } } @@ -88,6 +99,78 @@ class VirtualFunctionCallPreparationInterpreter( result } + /** + * This function interprets an arbitrary [[VirtualFunctionCall]]. If this method returns a + * [[Result]] instance, the interpretation of this call is already done. Otherwise, a new + * analysis was triggered whose result is not yet ready. In this case, the result needs to be + * finalized later on. + */ + private def interpretArbitraryCall(instr: T, defSite: Int): ProperPropertyComputationResult = { + val (methods, _) = getMethodsForPC( + instr.pc, ps, state.callees.get, declaredMethods + ) + + if (methods.isEmpty) { + return Result(instr, StringConstancyProperty.lb) + } + + // Collect all parameters (do this here to evaluate them only once) + // TODO: Current assumption: Results of parameters are available right away + val paramScis = instr.params.map { p ⇒ + StringConstancyProperty.extractFromPPCR( + exprHandler.processDefSite(p.asVar.definedBy.head) + ).stringConstancyInformation + }.toList + + val results = methods.map { nextMethod ⇒ + val tac = getTACAI(ps, nextMethod, state) + if (tac.isDefined) { + // TAC available => Get return UVar and start the string analysis + val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get + val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + val entity = (uvar, nextMethod) + + InterproceduralStringAnalysis.registerParams(entity, paramScis) + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case r: Result ⇒ + state.appendResultToFpe2Sci(defSite, r) + r + case _ ⇒ + if (!state.dependees.contains(nextMethod)) { + state.dependees(nextMethod) = ListBuffer() + } + state.dependees(nextMethod).append(eps) + state.var2IndexMapping(uvar) = defSite + InterimResult( + entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + List(), + c + ) + } + } else { + // No TAC => Register dependee and continue + InterimResult( + nextMethod, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees.values.flatten, + c + ) + } + } + + val finalResults = results.filter(_.isInstanceOf[Result]) + val intermediateResults = results.filter(!_.isInstanceOf[Result]) + if (results.length == finalResults.length) { + finalResults.head + } else { + intermediateResults.head + } + } + /** * Function for processing calls to [[StringBuilder#append]] or [[StringBuffer#append]]. Note * that this function assumes that the given `appendCall` is such a function call! Otherwise, @@ -139,7 +222,6 @@ class VirtualFunctionCallPreparationInterpreter( ) } - state.appendResultToFpe2Sci(defSite, finalSci) val e: Integer = defSite Result(e, StringConstancyProperty(finalSci)) } @@ -225,7 +307,7 @@ class VirtualFunctionCallPreparationInterpreter( valueSci } - state.appendResultToFpe2Sci(defSiteHead, valueSci) + state.appendToFpe2Sci(defSiteHead, valueSci, reset = true) val e: Integer = defSiteHead Result(e, StringConstancyProperty(finalSci)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala index 9850fe4ca3..c93c963252 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer +import scala.collection.mutable.ListBuffer + import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState @@ -26,9 +28,9 @@ class ArrayFinalizer( */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, cfg) - state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple( - allDefSites.sorted.map { state.fpe2sci(_) } - ) + state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( + allDefSites.sorted.flatMap(state.fpe2sci(_)) + )) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala index 4e497f30e5..fe38e533d1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala @@ -20,7 +20,11 @@ class NonVirtualMethodCallFinalizer(state: ComputationState) extends AbstractFin */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } - state.fpe2sci(defSite) = StringConstancyInformation.reduceMultiple(scis.toList) + state.appendToFpe2Sci( + defSite, + StringConstancyInformation.reduceMultiple(scis.flatten.toList), + reset = true + ) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 0e325514f9..96e82acb6b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -39,9 +39,11 @@ class VirtualFunctionCallFinalizer( */ private def finalizeAppend(instr: T, defSite: Int): Unit = { val receiverSci = StringConstancyInformation.reduceMultiple( - instr.receiver.asVar.definedBy.toArray.sorted.map(state.fpe2sci(_)).toList + instr.receiver.asVar.definedBy.toArray.sorted.flatMap(state.fpe2sci(_)) + ) + val appendSci = StringConstancyInformation.reduceMultiple( + state.fpe2sci(instr.params.head.asVar.definedBy.head) ) - val appendSci = state.fpe2sci(instr.params.head.asVar.definedBy.head) val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { receiverSci @@ -59,7 +61,7 @@ class VirtualFunctionCallFinalizer( ) } - state.fpe2sci(defSite) = finalSci + state.appendToFpe2Sci(defSite, finalSci, reset = true) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 0ac85eafc3..2d26384463 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -32,11 +32,13 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { * Accumulator function for transforming a path into a StringTree element. */ private def pathToTreeAcc( - subpath: SubPath, fpe2Sci: Map[Int, StringConstancyInformation] + subpath: SubPath, fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] ): Option[StringTree] = { subpath match { case fpe: FlatPathElement ⇒ - val sci = if (fpe2Sci.contains(fpe.element)) fpe2Sci(fpe.element) else { + val sci = if (fpe2Sci.contains(fpe.element)) { + StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element).toList) + } else { val r = interpretationHandler.processDefSite(fpe.element).asInstanceOf[Result] r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } @@ -122,8 +124,8 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { */ def pathToStringTree( path: Path, - fpe2Sci: Map[Int, StringConstancyInformation] = Map.empty, - resetExprHandler: Boolean = true + fpe2Sci: Map[Int, ListBuffer[StringConstancyInformation]] = Map.empty, + resetExprHandler: Boolean = true ): StringTree = { val tree = path.elements.size match { case 1 ⇒ pathToTreeAcc(path.elements.head, fpe2Sci).get From 459eddd07476caf788182568347184387be5480c Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Feb 2019 14:22:06 +0100 Subject: [PATCH 172/316] As one parameter of StringConstancyInformation#reduceMultiple was changed to iterable, some calls to that function could be slightly simplified. --- .../string_analysis/IntraproceduralStringAnalysis.scala | 2 +- .../InterproceduralNonVirtualMethodCallInterpreter.scala | 2 +- .../intraprocedural/IntraproceduralArrayInterpreter.scala | 2 +- .../IntraproceduralNonVirtualMethodCallInterpreter.scala | 2 +- .../string_analysis/preprocessing/PathTransformer.scala | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index 7469d9b451..b0a9c91af6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -147,7 +147,7 @@ class IntraproceduralStringAnalysis( uvar.definedBy.toArray.sorted.map { ds ⇒ val r = interHandler.processDefSite(ds).asInstanceOf[Result] r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - }.toList + } ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index 7d49c6fe0f..1328fef463 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -83,7 +83,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( // Final result is available val scis = results.map(r ⇒ StringConstancyProperty.extractFromPPCR(r._2).stringConstancyInformation) - val reduced = StringConstancyInformation.reduceMultiple(scis.toList) + val reduced = StringConstancyInformation.reduceMultiple(scis) Result(defSite, StringConstancyProperty(reduced)) } else { // Some intermediate results => register necessary information from final diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala index ceffca3516..9e8fd24f1f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala @@ -76,7 +76,7 @@ class IntraproceduralArrayInterpreter( Result(instr, StringConstancyProperty( StringConstancyInformation.reduceMultiple( - children.filter(!_.isTheNeutralElement).toList + children.filter(!_.isTheNeutralElement) ) )) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala index c1d8e48d3f..f541218e30 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala @@ -75,7 +75,7 @@ class IntraproceduralNonVirtualMethodCallInterpreter( r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation ) } - val reduced = StringConstancyInformation.reduceMultiple(scis.toList) + val reduced = StringConstancyInformation.reduceMultiple(scis) StringConstancyProperty(reduced) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 2d26384463..cd9dbb7bdb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -37,7 +37,7 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { subpath match { case fpe: FlatPathElement ⇒ val sci = if (fpe2Sci.contains(fpe.element)) { - StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element).toList) + StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) } else { val r = interpretationHandler.processDefSite(fpe.element).asInstanceOf[Result] r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation From 4da38eb5b97155d6794c3337b006a108b7e8c7ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Feb 2019 17:59:40 +0100 Subject: [PATCH 173/316] 1) Changed the InterproceduralStringAnalysis to work context-insensitive 2) The InterproceduralStringAnalysis now retrieves the TAC via the property store --- .../InterproceduralTestMethods.java | 20 ++++- .../InterproceduralStringAnalysis.scala | 85 ++++++++++++++++--- .../AbstractStringInterpreter.scala | 48 +++++++++++ .../InterpretationHandler.scala | 2 +- .../ArrayPreparationInterpreter.scala | 9 +- ...InterproceduralInterpretationHandler.scala | 6 +- ...ceduralStaticFunctionCallInterpreter.scala | 22 ++--- ...alFunctionCallPreparationInterpreter.scala | 22 +++-- ...IntraproceduralInterpretationHandler.scala | 2 +- 9 files changed, 172 insertions(+), 44 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index b1b67c18c6..2c6a81afac 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -75,7 +75,7 @@ public void initFromNonVirtualFunctionCallTest(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "java.lang.Integer" + expectedStrings = "java.lang.(Integer|Object|Runtime)" ) }) public void fromStaticMethodWithParamTest() { @@ -122,7 +122,7 @@ public void methodOutOfScopeTest() throws FileNotFoundException { @StringDefinitions( expectedLevel = DYNAMIC, expectedStrings = "(java.lang.Object|java.lang.Runtime|" - + "java.lang.Integer|\\w)" + + "java.lang.(Integer|Object|Runtime)|\\w)" ) }) @@ -224,6 +224,22 @@ public void unknownHierarchyInstanceTest(GreetingService greetingService) { analyzeString(greetingService.getGreeting("World")); } + @StringDefinitionsCollection( + value = "a case where context-insensitivity is tested", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "java.lang.(Integer|Object|Runtime)" + ) + + }) + public void contextInsensitivityTest() { + StringBuilder sb = new StringBuilder(); + String s = StringProvider.getFQClassName("java.lang", "Object"); + sb.append(StringProvider.getFQClassName("java.lang", "Runtime")); + analyzeString(sb.toString()); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index f5b1a0cd48..05fdf9deac 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -15,6 +15,7 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject @@ -40,6 +41,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedura import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode /** * This class is to be used to store state information that are required at a later point in @@ -50,6 +54,7 @@ case class ComputationState( var computedLeanPath: Option[Path] = None, var callees: Option[Callees] = None ) { + var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ var cfg: CFG[Stmt[V], TACStmts[V]] = _ // If not empty, this very routine can only produce an intermediate result val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() @@ -59,7 +64,7 @@ case class ComputationState( val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() // Parameter values of method / function; a mapping from the definition sites of parameter ( // negative values) to a correct index of `params` has to be made! - var params: List[StringConstancyInformation] = List() + var params: List[Seq[StringConstancyInformation]] = List() /** * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, @@ -128,19 +133,37 @@ class InterproceduralStringAnalysis( dm.declaringClassType.toJava == data._2.classFile.thisType.toJava }.get + val tacai = ps(data._2, TACAI.key) + if (tacai.hasUBP) { + state.tac = tacai.ub.tac.get + } else { + if (!state.dependees.contains(data)) { + state.dependees(data) = ListBuffer() + } + state.dependees(data).append(tacai) + InterimResult( + tacai, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees.values.flatten, + continuation(data, state) + ) + } + val calleesEOptP = ps(dm, Callees.key) if (calleesEOptP.hasUBP) { state.callees = Some(calleesEOptP.ub) determinePossibleStrings(data, state) } else { - state.dependees(data) = ListBuffer() + if (!state.dependees.contains(data)) { + state.dependees(data) = ListBuffer() + } state.dependees(data).append(calleesEOptP) - val dependees = state.dependees.values.flatten InterimResult( calleesEOptP, StringConstancyProperty.lb, StringConstancyProperty.ub, - dependees, + state.dependees.values.flatten, continuation(data, state) ) } @@ -245,6 +268,7 @@ class InterproceduralStringAnalysis( continuation(data, state) ) } else { + InterproceduralStringAnalysis.unregisterParams(data) Result(data, StringConstancyProperty(sci)) } } @@ -264,14 +288,43 @@ class InterproceduralStringAnalysis( state: ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { eps.pk match { + case TACAI.key ⇒ eps match { + case FinalP(tac: TACAI) ⇒ + state.tac = tac.tac.get + state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) + if (state.dependees(inputData).isEmpty) { + state.dependees.remove(inputData) + determinePossibleStrings(inputData, state) + } else { + InterimResult( + inputData, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees.values.flatten, + continuation(inputData, state) + ) + } + case InterimLUBP(lb, ub) ⇒ InterimResult( + inputData, lb, ub, state.dependees.values.flatten, continuation(inputData, state) + ) + case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") + } case Callees.key ⇒ eps match { case FinalP(callees: Callees) ⇒ state.callees = Some(callees) state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) if (state.dependees(inputData).isEmpty) { state.dependees.remove(inputData) + determinePossibleStrings(inputData, state) + } else { + InterimResult( + inputData, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees.values.flatten, + continuation(inputData, state) + ) } - determinePossibleStrings(inputData, state) case InterimLUBP(lb, ub) ⇒ InterimResult( inputData, lb, ub, state.dependees.values.flatten, continuation(inputData, state) ) @@ -321,6 +374,7 @@ class InterproceduralStringAnalysis( val finalSci = new PathTransformer(null).pathToStringTree( state.computedLeanPath.get, state.fpe2sci.toMap, resetExprHandler = false ).reduce(true) + InterproceduralStringAnalysis.unregisterParams(data) Result(data, StringConstancyProperty(finalSci)) } @@ -486,19 +540,26 @@ class InterproceduralStringAnalysis( object InterproceduralStringAnalysis { - private val paramInfos = mutable.Map[Entity, List[StringConstancyInformation]]() + /** + * Maps entities to a list of lists of parameters. As currently this analysis works context- + * insensitive, we have a list of lists to capture all parameters of all potential method / + * function calls. + */ + private val paramInfos = mutable.Map[Entity, ListBuffer[Seq[StringConstancyInformation]]]() - def registerParams(e: Entity, scis: List[StringConstancyInformation]): Unit = { + def registerParams(e: Entity, scis: List[Seq[StringConstancyInformation]]): Unit = { if (!paramInfos.contains(e)) { - paramInfos(e) = List(scis: _*) + paramInfos(e) = ListBuffer(scis: _*) + } else { + paramInfos(e).appendAll(scis) } - // Per entity and method, a StringConstancyInformation list should be present only once, - // thus no else branch } - def getParams(e: Entity): List[StringConstancyInformation] = + def unregisterParams(e: Entity): Unit = paramInfos.remove(e) + + def getParams(e: Entity): List[Seq[StringConstancyInformation]] = if (paramInfos.contains(e)) { - paramInfos(e) + paramInfos(e).toList } else { List() } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index b25f0d2e48..b6538da49d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -5,11 +5,14 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.value.ValueInformation import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -17,6 +20,12 @@ import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.Assignment +import org.opalj.tac.DUVar +import org.opalj.tac.Expr +import org.opalj.tac.ExprStmt +import org.opalj.tac.FunctionCall +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler /** * @param cfg The control flow graph that underlies the instruction to interpret. @@ -82,6 +91,45 @@ abstract class AbstractStringInterpreter( (methods.sortBy(_.classFile.fqn).toList, hasMethodWithUnknownBody) } + /** + * `getParametersForPCs` takes a list of program counters, `pcs`, as well as the TACode on which + * `pcs` is based. This function then extracts the parameters of all function calls from the + * given `pcs` and returns them. + */ + protected def getParametersForPCs( + pcs: Iterable[Int], + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + ): List[Seq[Expr[V]]] = { + val paramLists = ListBuffer[Seq[Expr[V]]]() + pcs.map(tac.pcToIndex).foreach { stmtIndex ⇒ + val params = tac.stmts(stmtIndex) match { + case ExprStmt(_, vfc: FunctionCall[V]) ⇒ vfc.params + case Assignment(_, _, fc: FunctionCall[V]) ⇒ fc.params + case _ ⇒ Seq() + } + if (params.nonEmpty) { + paramLists.append(params) + } + } + paramLists.toList + } + + /** + * evaluateParameters takes a list of parameters, `params`, as produced, e.g., by + * [[AbstractStringInterpreter.getParametersForPCs]], and an interpretation handler, `iHandler` + * and interprets the given parameters. + */ + protected def evaluateParameters( + params: List[Seq[Expr[V]]], + iHandler: InterproceduralInterpretationHandler + ): List[Seq[StringConstancyInformation]] = params.map(_.map { expr ⇒ + val scis = expr.asVar.definedBy.map(iHandler.processDefSite(_, List())).map { r ⇒ + // TODO: Current assumption: Results of parameters are available right away + StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + } + StringConstancyInformation.reduceMultiple(scis) + }) + /** * * @param instr The instruction that is to be interpreted. It is the responsibility of diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 27784809cb..c7f1d95ebb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -52,7 +52,7 @@ abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { */ def processDefSite( defSite: Int, - params: List[StringConstancyInformation] = List() + params: List[Seq[StringConstancyInformation]] = List() ): ProperPropertyComputationResult /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 0588110017..d216de19b6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -34,7 +34,7 @@ class ArrayPreparationInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, state: ComputationState, - params: List[StringConstancyInformation] + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = ArrayLoad[V] @@ -66,12 +66,7 @@ class ArrayPreparationInterpreter( defSites.filter(_ < 0).foreach { ds ⇒ val paramPos = Math.abs(defSite + 2) // lb is the fallback value - var sci = StringConstancyInformation( - possibleStrings = StringConstancyInformation.UnknownWordSymbol - ) - if (paramPos < params.size) { - sci = params(paramPos) - } + val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) val e: Integer = ds state.appendResultToFpe2Sci(ds, Result(e, StringConstancyProperty(sci))) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 3fe3cba748..9aafcb27f8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -68,7 +68,7 @@ class InterproceduralInterpretationHandler( * @inheritdoc */ override def processDefSite( - defSite: Int, params: List[StringConstancyInformation] = List() + defSite: Int, params: List[Seq[StringConstancyInformation]] = List() ): ProperPropertyComputationResult = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" @@ -78,7 +78,9 @@ class InterproceduralInterpretationHandler( return Result(e, StringConstancyProperty.lb) } else if (defSite < 0) { val paramPos = Math.abs(defSite + 2) - return Result(e, StringConstancyProperty(params(paramPos))) + val paramScis = params.map(_(paramPos)).distinct + val finalParamSci = StringConstancyInformation.reduceMultiple(paramScis) + return Result(e, StringConstancyProperty(finalParamSci)) } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index e6539db307..7fd7b2d620 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -18,8 +18,8 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue -import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -66,20 +66,22 @@ class InterproceduralStaticFunctionCallInterpreter( val m = methods._1.head val tac = getTACAI(ps, m, state) + + val directCallSites = state.callees.get.directCallSites()(ps, declaredMethods) + val relevantPCs = directCallSites.filter { + case (_, calledMethods) ⇒ + calledMethods.exists(m ⇒ + m.name == instr.name && m.declaringClassType == instr.declaringClass) + }.keys + // Collect all parameters + val params = evaluateParameters(getParametersForPCs(relevantPCs, state.tac), exprHandler) + if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, m) - - // Collect all parameters - // TODO: Current assumption: Results of parameters are available right away - val paramScis = instr.params.map { p ⇒ - StringConstancyProperty.extractFromPPCR( - exprHandler.processDefSite(p.asVar.definedBy.head) - ).stringConstancyInformation - }.toList - InterproceduralStringAnalysis.registerParams(entity, paramScis) + InterproceduralStringAnalysis.registerParams(entity, params) val eps = ps(entity, StringConstancyProperty.key) eps match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index e8365c92fc..83d7d79edc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -42,7 +42,7 @@ class VirtualFunctionCallPreparationInterpreter( ps: PropertyStore, state: ComputationState, declaredMethods: DeclaredMethods, - params: List[StringConstancyInformation], + params: List[Seq[StringConstancyInformation]], c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { @@ -114,13 +114,17 @@ class VirtualFunctionCallPreparationInterpreter( return Result(instr, StringConstancyProperty.lb) } - // Collect all parameters (do this here to evaluate them only once) - // TODO: Current assumption: Results of parameters are available right away - val paramScis = instr.params.map { p ⇒ - StringConstancyProperty.extractFromPPCR( - exprHandler.processDefSite(p.asVar.definedBy.head) - ).stringConstancyInformation - }.toList + val directCallSites = state.callees.get.directCallSites()(ps, declaredMethods) + val instrClassName = + instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava + val relevantPCs = directCallSites.filter { + case (_, calledMethods) ⇒ calledMethods.exists { m ⇒ + val mClassName = m.declaringClassType.toJava + m.name == instr.name && mClassName == instrClassName + } + }.keys + // Collect all parameters + val params = evaluateParameters(getParametersForPCs(relevantPCs, state.tac), exprHandler) val results = methods.map { nextMethod ⇒ val tac = getTACAI(ps, nextMethod, state) @@ -130,7 +134,7 @@ class VirtualFunctionCallPreparationInterpreter( val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, nextMethod) - InterproceduralStringAnalysis.registerParams(entity, paramScis) + InterproceduralStringAnalysis.registerParams(entity, params) val eps = ps(entity, StringConstancyProperty.key) eps match { case r: Result ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala index 4f3360d990..86b442d9a1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala @@ -55,7 +55,7 @@ class IntraproceduralInterpretationHandler( * @inheritdoc */ override def processDefSite( - defSite: Int, params: List[StringConstancyInformation] = List() + defSite: Int, params: List[Seq[StringConstancyInformation]] = List() ): ProperPropertyComputationResult = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" From 7646f064d7552e2b331da043bd3f38f4580c657b Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 15:47:59 +0100 Subject: [PATCH 174/316] 1) Fixed the usage of EPS handling 2) Minor changes on the ComputationState class --- .../InterproceduralStringAnalysis.scala | 94 +++++++++++-------- ...InterproceduralInterpretationHandler.scala | 2 +- ...ralNonVirtualFunctionCallInterpreter.scala | 8 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...alFunctionCallPreparationInterpreter.scala | 4 +- 5 files changed, 65 insertions(+), 47 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 05fdf9deac..af0a0a62ca 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -49,14 +49,19 @@ import org.opalj.tac.TACode * This class is to be used to store state information that are required at a later point in * time during the analysis, e.g., due to the fact that another analysis had to be triggered to * have all required information ready for a final result. + * + * @param entity The entity for which the analysis was started with. */ -case class ComputationState( - var computedLeanPath: Option[Path] = None, - var callees: Option[Callees] = None -) { +case class ComputationState(entity: P) { + // The Three-Address Code of the entity's method var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ + // The Control Flow Graph of the entity's method var cfg: CFG[Stmt[V], TACStmts[V]] = _ - // If not empty, this very routine can only produce an intermediate result + // The computed lean path that corresponds to the given entity + var computedLeanPath: Path = _ + // Callees information regarding the declared method that corresponds to the entity's method + var callees: Callees = _ + // If not empty, this routine can only produce an intermediate result val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() // A mapping from DUVar elements to the corresponding indices of the FlatPathElements val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() @@ -125,7 +130,7 @@ class InterproceduralStringAnalysis( private var declaredMethods: DeclaredMethods = _ def analyze(data: P): ProperPropertyComputationResult = { - val state = ComputationState() + val state = ComputationState(data) declaredMethods = project.get(DeclaredMethodsKey) // TODO: Is there a way to get the declared method in constant time? val dm = declaredMethods.declaredMethods.find { dm ⇒ @@ -142,17 +147,17 @@ class InterproceduralStringAnalysis( } state.dependees(data).append(tacai) InterimResult( - tacai, + data, StringConstancyProperty.lb, StringConstancyProperty.ub, state.dependees.values.flatten, - continuation(data, state) + continuation(state) ) } val calleesEOptP = ps(dm, Callees.key) if (calleesEOptP.hasUBP) { - state.callees = Some(calleesEOptP.ub) + state.callees = calleesEOptP.ub determinePossibleStrings(data, state) } else { if (!state.dependees.contains(data)) { @@ -160,11 +165,11 @@ class InterproceduralStringAnalysis( } state.dependees(data).append(calleesEOptP) InterimResult( - calleesEOptP, + data, StringConstancyProperty.lb, StringConstancyProperty.ub, state.dependees.values.flatten, - continuation(data, state) + continuation(state) ) } } @@ -202,10 +207,10 @@ class InterproceduralStringAnalysis( } val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) - state.computedLeanPath = Some(paths.makeLeanPath(uvar, stmts)) + state.computedLeanPath = paths.makeLeanPath(uvar, stmts) // Find DUVars, that the analysis of the current entity depends on - val dependentVars = findDependentVars(state.computedLeanPath.get, stmts, uvar) + val dependentVars = findDependentVars(state.computedLeanPath, stmts, uvar) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar ⇒ val toAnalyze = (nextVar, data._2) @@ -223,17 +228,17 @@ class InterproceduralStringAnalysis( } } else { val iHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, continuation(data, state) + state.cfg, ps, declaredMethods, state, continuation(state) ) - if (computeResultsForPath(state.computedLeanPath.get, iHandler, state)) { + if (computeResultsForPath(state.computedLeanPath, iHandler, state)) { sci = new PathTransformer(iHandler).pathToStringTree( - state.computedLeanPath.get, state.fpe2sci.toMap + state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) } } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - state.computedLeanPath = Some(if (defSites.length == 1) { + state.computedLeanPath = if (defSites.length == 1) { // Trivial case for just one element Path(List(FlatPathElement(defSites.head))) } else { @@ -244,14 +249,14 @@ class InterproceduralStringAnalysis( children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) } Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) - }) + } val iHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, continuation(data, state) + state.cfg, ps, declaredMethods, state, continuation(state) ) - if (computeResultsForPath(state.computedLeanPath.get, iHandler, state)) { + if (computeResultsForPath(state.computedLeanPath, iHandler, state)) { sci = new PathTransformer(iHandler).pathToStringTree( - state.computedLeanPath.get, state.fpe2sci.toMap + state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) } // No need to cover the else branch: interimResults.nonEmpty => dependees were added to @@ -265,7 +270,7 @@ class InterproceduralStringAnalysis( StringConstancyProperty.ub, StringConstancyProperty.lb, state.dependees.values.flatten, - continuation(data, state) + continuation(state) ) } else { InterproceduralStringAnalysis.unregisterParams(data) @@ -279,14 +284,13 @@ class InterproceduralStringAnalysis( * @param state The current computation state. Within this continuation, dependees of the state * might be updated. Furthermore, methods processing this continuation might alter * the state. - * @param inputData The data which the string analysis was started with. * @return Returns a final result if (already) available. Otherwise, an intermediate result will * be returned. */ private def continuation( - inputData: P, - state: ComputationState + state: ComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { + val inputData = state.entity eps.pk match { case TACAI.key ⇒ eps match { case FinalP(tac: TACAI) ⇒ @@ -301,17 +305,17 @@ class InterproceduralStringAnalysis( StringConstancyProperty.lb, StringConstancyProperty.ub, state.dependees.values.flatten, - continuation(inputData, state) + continuation(state) ) } case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees.values.flatten, continuation(inputData, state) + inputData, lb, ub, state.dependees.values.flatten, continuation(state) ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } case Callees.key ⇒ eps match { case FinalP(callees: Callees) ⇒ - state.callees = Some(callees) + state.callees = callees state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) if (state.dependees(inputData).isEmpty) { state.dependees.remove(inputData) @@ -322,21 +326,30 @@ class InterproceduralStringAnalysis( StringConstancyProperty.lb, StringConstancyProperty.ub, state.dependees.values.flatten, - continuation(inputData, state) + continuation(state) ) } case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees.values.flatten, continuation(inputData, state) + inputData, lb, ub, state.dependees.values.flatten, continuation(state) ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } case StringConstancyProperty.key ⇒ eps match { case FinalP(p) ⇒ - processFinalP(inputData, state, eps.e, p) - case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees.values.flatten, continuation(eps.e.asInstanceOf[P], state) - ) + processFinalP(state.entity, state, eps.e, p) + case InterimLUBP(lb, ub) ⇒ + for ((k, _) ← state.dependees) { + state.dependees(k) = state.dependees(k).filter(_.e != eps.e) + } + val eData = eps.e.asInstanceOf[P] + if (!state.dependees.contains(eData._2)) { + state.dependees(eData._2) = ListBuffer() + } + state.dependees(eData._2).append(eps) + InterimResult( + inputData, lb, ub, state.dependees.values.flatten, continuation(state) + ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } } @@ -370,9 +383,9 @@ class InterproceduralStringAnalysis( private def computeFinalResult( data: P, state: ComputationState, iHandler: InterproceduralInterpretationHandler ): Result = { - finalizePreparations(state.computedLeanPath.get, state, iHandler) + finalizePreparations(state.computedLeanPath, state, iHandler) val finalSci = new PathTransformer(null).pathToStringTree( - state.computedLeanPath.get, state.fpe2sci.toMap, resetExprHandler = false + state.computedLeanPath, state.fpe2sci.toMap, resetExprHandler = false ).reduce(true) InterproceduralStringAnalysis.unregisterParams(data) Result(data, StringConstancyProperty(finalSci)) @@ -395,11 +408,16 @@ class InterproceduralStringAnalysis( // No more dependees => Return the result for this analysis run state.dependees.foreach { case (k, v) ⇒ state.dependees(k) = v.filter(_.e != e) } + state.dependees.foreach { + case (k, v) ⇒ if (v.isEmpty) { + state.dependees.remove(k) + } + } val remDependees = state.dependees.values.flatten if (remDependees.isEmpty) { val iHandler = InterproceduralInterpretationHandler( state.cfg, ps, declaredMethods, state, - continuation(data, state) + continuation(state) ) computeFinalResult(data, state, iHandler) } else { @@ -408,7 +426,7 @@ class InterproceduralStringAnalysis( StringConstancyProperty.ub, StringConstancyProperty.lb, remDependees, - continuation(data, state) + continuation(state) ) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 9aafcb27f8..4ae70293e2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -86,7 +86,7 @@ class InterproceduralInterpretationHandler( } processedDefSites.append(defSite) - val callees = state.callees.get + val callees = state.callees stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ val result = new StringConstInterpreter(cfg, this).interpret(expr, defSite) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 10c227c1d8..f84267ca16 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -51,7 +51,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val methods = getMethodsForPC(instr.pc, ps, state.callees.get, declaredMethods) + val methods = getMethodsForPC(instr.pc, ps, state.callees, declaredMethods) val m = methods._1.head val tac = getTACAI(ps, m, state) if (tac.isDefined) { @@ -71,17 +71,17 @@ class InterproceduralNonVirtualFunctionCallInterpreter( state.dependees(m).append(eps) state.var2IndexMapping(uvar) = defSite InterimResult( - entity, + state.entity, StringConstancyProperty.lb, StringConstancyProperty.ub, - List(), + state.dependees.values.flatten, c ) } } else { // No TAC => Register dependee and continue InterimResult( - m, + state.entity, StringConstancyProperty.lb, StringConstancyProperty.ub, state.dependees.values.flatten, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 7fd7b2d620..8e4c35cfd0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -54,7 +54,7 @@ class InterproceduralStaticFunctionCallInterpreter( */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val methods, _ = getMethodsForPC( - instr.pc, ps, state.callees.get, declaredMethods + instr.pc, ps, state.callees, declaredMethods ) // Static methods cannot be overwritten, thus 1) we do not need the second return value of @@ -67,7 +67,7 @@ class InterproceduralStaticFunctionCallInterpreter( val m = methods._1.head val tac = getTACAI(ps, m, state) - val directCallSites = state.callees.get.directCallSites()(ps, declaredMethods) + val directCallSites = state.callees.directCallSites()(ps, declaredMethods) val relevantPCs = directCallSites.filter { case (_, calledMethods) ⇒ calledMethods.exists(m ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 83d7d79edc..d5a5d0943a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -107,14 +107,14 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretArbitraryCall(instr: T, defSite: Int): ProperPropertyComputationResult = { val (methods, _) = getMethodsForPC( - instr.pc, ps, state.callees.get, declaredMethods + instr.pc, ps, state.callees, declaredMethods ) if (methods.isEmpty) { return Result(instr, StringConstancyProperty.lb) } - val directCallSites = state.callees.get.directCallSites()(ps, declaredMethods) + val directCallSites = state.callees.directCallSites()(ps, declaredMethods) val instrClassName = instr.receiver.asVar.value.asReferenceValue.asReferenceType.mostPreciseObjectType.toJava val relevantPCs = directCallSites.filter { From a98fdac4b4b443e2b1193d37e5f7148ddcbf4eea Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 16:03:23 +0100 Subject: [PATCH 175/316] Added the computation state of the InterproceduralStringAnalysis into its own file. --- .../InterproceduralStringAnalysis.scala | 80 +++---------------- .../AbstractStringInterpreter.scala | 4 +- .../ArrayPreparationInterpreter.scala | 4 +- ...InterproceduralInterpretationHandler.scala | 12 +-- ...ralNonVirtualFunctionCallInterpreter.scala | 4 +- ...duralNonVirtualMethodCallInterpreter.scala | 4 +- ...ceduralStaticFunctionCallInterpreter.scala | 4 +- ...alFunctionCallPreparationInterpreter.scala | 8 +- .../finalizer/AbstractFinalizer.scala | 4 +- .../finalizer/ArrayFinalizer.scala | 4 +- .../NonVirtualMethodCallFinalizer.scala | 6 +- .../VirtualFunctionCallFinalizer.scala | 4 +- 12 files changed, 42 insertions(+), 96 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index af0a0a62ca..06df555d1d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -5,7 +5,6 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult @@ -15,11 +14,9 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS -import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler @@ -27,7 +24,6 @@ import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.properties.TACAI @@ -41,62 +37,6 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedura import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath -import org.opalj.tac.DUVar -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode - -/** - * This class is to be used to store state information that are required at a later point in - * time during the analysis, e.g., due to the fact that another analysis had to be triggered to - * have all required information ready for a final result. - * - * @param entity The entity for which the analysis was started with. - */ -case class ComputationState(entity: P) { - // The Three-Address Code of the entity's method - var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ - // The Control Flow Graph of the entity's method - var cfg: CFG[Stmt[V], TACStmts[V]] = _ - // The computed lean path that corresponds to the given entity - var computedLeanPath: Path = _ - // Callees information regarding the declared method that corresponds to the entity's method - var callees: Callees = _ - // If not empty, this routine can only produce an intermediate result - val dependees: mutable.Map[Entity, ListBuffer[EOptionP[Entity, Property]]] = mutable.Map() - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() - // A mapping from values / indices of FlatPathElements to StringConstancyInformation - val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() - // Parameter values of method / function; a mapping from the definition sites of parameter ( - // negative values) to a correct index of `params` has to be made! - var params: List[Seq[StringConstancyInformation]] = List() - - /** - * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, - * however, only if `defSite` is not yet present. - */ - def appendResultToFpe2Sci( - defSite: Int, r: Result, reset: Boolean = false - ): Unit = appendToFpe2Sci( - defSite, - StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation, - reset - ) - - /** - * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] - * map accordingly, however, only if `defSite` is not yet present. - */ - def appendToFpe2Sci( - defSite: Int, sci: StringConstancyInformation, reset: Boolean = false - ): Unit = { - if (reset || !fpe2sci.contains(defSite)) { - fpe2sci(defSite) = ListBuffer() - } - fpe2sci(defSite).append(sci) - } - -} /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -130,7 +70,7 @@ class InterproceduralStringAnalysis( private var declaredMethods: DeclaredMethods = _ def analyze(data: P): ProperPropertyComputationResult = { - val state = ComputationState(data) + val state = InterproceduralComputationState(data) declaredMethods = project.get(DeclaredMethodsKey) // TODO: Is there a way to get the declared method in constant time? val dm = declaredMethods.declaredMethods.find { dm ⇒ @@ -180,7 +120,7 @@ class InterproceduralStringAnalysis( * [[InterimResult]] depending on whether other information needs to be computed first. */ private def determinePossibleStrings( - data: P, state: ComputationState + data: P, state: InterproceduralComputationState ): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation @@ -288,7 +228,7 @@ class InterproceduralStringAnalysis( * be returned. */ private def continuation( - state: ComputationState + state: InterproceduralComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { val inputData = state.entity eps.pk match { @@ -356,7 +296,9 @@ class InterproceduralStringAnalysis( } private def finalizePreparations( - path: Path, state: ComputationState, iHandler: InterproceduralInterpretationHandler + path: Path, + state: InterproceduralComputationState, + iHandler: InterproceduralInterpretationHandler ): Unit = path.elements.foreach { case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { @@ -381,7 +323,9 @@ class InterproceduralStringAnalysis( * @return Returns the final result. */ private def computeFinalResult( - data: P, state: ComputationState, iHandler: InterproceduralInterpretationHandler + data: P, + state: InterproceduralComputationState, + iHandler: InterproceduralInterpretationHandler ): Result = { finalizePreparations(state.computedLeanPath, state, iHandler) val finalSci = new PathTransformer(null).pathToStringTree( @@ -397,7 +341,7 @@ class InterproceduralStringAnalysis( */ private def processFinalP( data: P, - state: ComputationState, + state: InterproceduralComputationState, e: Entity, p: Property ): ProperPropertyComputationResult = { @@ -438,13 +382,13 @@ class InterproceduralStringAnalysis( * @param p The path to traverse. * @param iHandler The handler for interpreting string related sites. * @param state The current state of the computation. This function will alter - * [[ComputationState.fpe2sci]]. + * [[InterproceduralComputationState.fpe2sci]]. * @return Returns `true` if all values computed for the path are final results. */ private def computeResultsForPath( p: Path, iHandler: InterproceduralInterpretationHandler, - state: ComputationState + state: InterproceduralComputationState ): Boolean = { var hasFinalResult = true diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index b6538da49d..1a451b895b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -18,7 +18,6 @@ import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.Assignment import org.opalj.tac.DUVar @@ -26,6 +25,7 @@ import org.opalj.tac.Expr import org.opalj.tac.ExprStmt import org.opalj.tac.FunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * @param cfg The control flow graph that underlies the instruction to interpret. @@ -58,7 +58,7 @@ abstract class AbstractStringInterpreter( protected def getTACAI( ps: PropertyStore, m: Method, - s: ComputationState + s: InterproceduralComputationState ): Option[TACode[TACMethodParameter, V]] = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index d216de19b6..6296a31f6a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -13,9 +13,9 @@ import org.opalj.tac.ArrayStore import org.opalj.tac.Assignment import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * The `ArrayPreparationInterpreter` is responsible for preparing [[ArrayLoad]] as well as @@ -33,7 +33,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractString class ArrayPreparationInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, - state: ComputationState, + state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 4ae70293e2..53f5bb60ff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -15,7 +15,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment import org.opalj.tac.BinaryExpr +import org.opalj.tac.DoubleConst import org.opalj.tac.ExprStmt +import org.opalj.tac.FloatConst import org.opalj.tac.GetField import org.opalj.tac.IntConst import org.opalj.tac.New @@ -25,9 +27,6 @@ import org.opalj.tac.StaticFunctionCall import org.opalj.tac.StringConst import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState -import org.opalj.tac.DoubleConst -import org.opalj.tac.FloatConst import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryExprInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.FloatValueInterpreter @@ -38,6 +37,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInte import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.VirtualFunctionCallFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -57,7 +57,7 @@ class InterproceduralInterpretationHandler( cfg: CFG[Stmt[V], TACStmts[V]], ps: PropertyStore, declaredMethods: DeclaredMethods, - state: ComputationState, + state: InterproceduralComputationState, c: ProperOnUpdateContinuation ) extends InterpretationHandler(cfg) { @@ -154,7 +154,7 @@ class InterproceduralInterpretationHandler( } def finalizeDefSite( - defSite: Int, state: ComputationState + defSite: Int, state: InterproceduralComputationState ): Unit = { stmts(defSite) match { case nvmc: NonVirtualMethodCall[V] ⇒ @@ -180,7 +180,7 @@ object InterproceduralInterpretationHandler { cfg: CFG[Stmt[V], TACStmts[V]], ps: PropertyStore, declaredMethods: DeclaredMethods, - state: ComputationState, + state: InterproceduralComputationState, c: ProperOnUpdateContinuation ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( cfg, ps, declaredMethods, state, c diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index f84267ca16..a69e2c13cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -17,8 +17,8 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * The `InterproceduralNonVirtualFunctionCallInterpreter` is responsible for processing @@ -32,7 +32,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, - state: ComputationState, + state: InterproceduralComputationState, declaredMethods: DeclaredMethods, c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index 1328fef463..c4425db1ed 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -12,9 +12,9 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * The `InterproceduralNonVirtualMethodCallInterpreter` is responsible for processing @@ -31,7 +31,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( // but let it be instantiated in this class exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, - state: ComputationState, + state: InterproceduralComputationState, declaredMethods: DeclaredMethods, c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 8e4c35cfd0..fc037dfed0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -15,10 +15,10 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis /** @@ -35,7 +35,7 @@ class InterproceduralStaticFunctionCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, - state: ComputationState, + state: InterproceduralComputationState, declaredMethods: DeclaredMethods, c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index d5a5d0943a..631bfe758e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -23,8 +23,8 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis /** @@ -40,7 +40,7 @@ class VirtualFunctionCallPreparationInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, - state: ComputationState, + state: InterproceduralComputationState, declaredMethods: DeclaredMethods, params: List[Seq[StringConstancyInformation]], c: ProperOnUpdateContinuation @@ -241,7 +241,7 @@ class VirtualFunctionCallPreparationInterpreter( * returned list contains an [[org.opalj.fpcf.InterimResult]]. */ private def receiverValuesOfAppendCall( - call: VirtualFunctionCall[V], state: ComputationState + call: VirtualFunctionCall[V], state: InterproceduralComputationState ): List[ProperPropertyComputationResult] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted @@ -266,7 +266,7 @@ class VirtualFunctionCallPreparationInterpreter( * This function can process string constants as well as function calls as argument to append. */ private def valueOfAppendCall( - call: VirtualFunctionCall[V], state: ComputationState + call: VirtualFunctionCall[V], state: InterproceduralComputationState ): ProperPropertyComputationResult = { val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala index 35b930faef..bb4bcee0bd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/AbstractFinalizer.scala @@ -1,7 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * When processing instruction interprocedurally, it is not always possible to compute a final @@ -17,7 +17,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState * @param state The computation state to use to retrieve partial results and to write the final * result back. */ -abstract class AbstractFinalizer(state: ComputationState) { +abstract class AbstractFinalizer(state: InterproceduralComputationState) { protected type T <: Any diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala index c93c963252..39c513b0b7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala @@ -5,18 +5,18 @@ import scala.collection.mutable.ListBuffer import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ArrayLoad import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * @author Patrick Mell */ class ArrayFinalizer( - state: ComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = ArrayLoad[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala index fe38e533d1..37ca70ef82 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala @@ -3,13 +3,15 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.NonVirtualMethodCall -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V /** * @author Patrick Mell */ -class NonVirtualMethodCallFinalizer(state: ComputationState) extends AbstractFinalizer(state) { +class NonVirtualMethodCallFinalizer( + state: InterproceduralComputationState +) extends AbstractFinalizer(state) { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 96e82acb6b..5f94f2f0e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -5,17 +5,17 @@ import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType -import org.opalj.tac.fpcf.analyses.string_analysis.ComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * @author Patrick Mell */ class VirtualFunctionCallFinalizer( - state: ComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { override type T = VirtualFunctionCall[V] From b6fca962ad318d73d72233e3203a34c311c9f442 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 16:08:53 +0100 Subject: [PATCH 176/316] Removed unnecessary arguments ("data: P"). --- .../InterproceduralStringAnalysis.scala | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 06df555d1d..1e064149b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -98,7 +98,7 @@ class InterproceduralStringAnalysis( val calleesEOptP = ps(dm, Callees.key) if (calleesEOptP.hasUBP) { state.callees = calleesEOptP.ub - determinePossibleStrings(data, state) + determinePossibleStrings(state) } else { if (!state.dependees.contains(data)) { state.dependees(data) = ListBuffer() @@ -120,21 +120,21 @@ class InterproceduralStringAnalysis( * [[InterimResult]] depending on whether other information needs to be computed first. */ private def determinePossibleStrings( - data: P, state: InterproceduralComputationState + state: InterproceduralComputationState ): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) - state.cfg = tacProvider(data._2).cfg - state.params = InterproceduralStringAnalysis.getParams(data) + state.cfg = tacProvider(state.entity._2).cfg + state.params = InterproceduralStringAnalysis.getParams(state.entity) val stmts = state.cfg.code.instructions - val uvar = data._1 + val uvar = state.entity._1 val defSites = uvar.definedBy.toArray.sorted // Function parameters are currently regarded as dynamic value; the following if finds read // operations of strings (not String{Builder, Buffer}s, they will be handles further down if (defSites.head < 0) { - return Result(data, StringConstancyProperty.lb) + return Result(state.entity, StringConstancyProperty.lb) } val pathFinder: AbstractPathFinder = new WindowPathFinder(state.cfg) @@ -143,7 +143,7 @@ class InterproceduralStringAnalysis( val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated if (initDefSites.isEmpty) { - return Result(data, StringConstancyProperty.lb) + return Result(state.entity, StringConstancyProperty.lb) } val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) @@ -153,12 +153,12 @@ class InterproceduralStringAnalysis( val dependentVars = findDependentVars(state.computedLeanPath, stmts, uvar) if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar ⇒ - val toAnalyze = (nextVar, data._2) + val toAnalyze = (nextVar, state.entity._2) dependentVars.foreach { case (k, v) ⇒ state.var2IndexMapping(k) = v } val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ - return processFinalP(data, state, ep.e, p) + return processFinalP(state.entity, state, ep.e, p) case _ ⇒ if (!state.dependees.contains(toAnalyze)) { state.dependees(toAnalyze) = ListBuffer() @@ -206,15 +206,15 @@ class InterproceduralStringAnalysis( if (state.dependees.values.nonEmpty) { InterimResult( - data, + state.entity, StringConstancyProperty.ub, StringConstancyProperty.lb, state.dependees.values.flatten, continuation(state) ) } else { - InterproceduralStringAnalysis.unregisterParams(data) - Result(data, StringConstancyProperty(sci)) + InterproceduralStringAnalysis.unregisterParams(state.entity) + Result(state.entity, StringConstancyProperty(sci)) } } @@ -238,7 +238,7 @@ class InterproceduralStringAnalysis( state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) if (state.dependees(inputData).isEmpty) { state.dependees.remove(inputData) - determinePossibleStrings(inputData, state) + determinePossibleStrings(state) } else { InterimResult( inputData, @@ -259,7 +259,7 @@ class InterproceduralStringAnalysis( state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) if (state.dependees(inputData).isEmpty) { state.dependees.remove(inputData) - determinePossibleStrings(inputData, state) + determinePossibleStrings(state) } else { InterimResult( inputData, From 76b9d22a4191bdbaf2516a466cfda71e968a3dea Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Feb 2019 16:35:03 +0100 Subject: [PATCH 177/316] Changed the "dependees" field in InterproceduralComputationState to a list. --- .../InterproceduralComputationState.scala | 74 ++++++++++++++++++ .../InterproceduralStringAnalysis.scala | 75 +++++++------------ .../AbstractStringInterpreter.scala | 5 +- ...ralNonVirtualFunctionCallInterpreter.scala | 11 +-- ...ceduralStaticFunctionCallInterpreter.scala | 9 +-- ...alFunctionCallPreparationInterpreter.scala | 9 +-- 6 files changed, 107 insertions(+), 76 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala new file mode 100644 index 0000000000..60a5085b03 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -0,0 +1,74 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property +import org.opalj.fpcf.Result +import org.opalj.value.ValueInformation +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.tac.Stmt +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.TACStmts + +/** + * This class is to be used to store state information that are required at a later point in + * time during the analysis, e.g., due to the fact that another analysis had to be triggered to + * have all required information ready for a final result. + * + * @param entity The entity for which the analysis was started with. + */ +case class InterproceduralComputationState(entity: P) { + // The Three-Address Code of the entity's method + var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ + // The Control Flow Graph of the entity's method + var cfg: CFG[Stmt[V], TACStmts[V]] = _ + // The computed lean path that corresponds to the given entity + var computedLeanPath: Path = _ + // Callees information regarding the declared method that corresponds to the entity's method + var callees: Callees = _ + // If not empty, this routine can only produce an intermediate result + var dependees: List[EOptionP[Entity, Property]] = List() + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() + // A mapping from values / indices of FlatPathElements to StringConstancyInformation + val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() + // Parameter values of method / function; a mapping from the definition sites of parameter ( + // negative values) to a correct index of `params` has to be made! + var params: List[Seq[StringConstancyInformation]] = List() + + /** + * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, + * however, only if `defSite` is not yet present. + */ + def appendResultToFpe2Sci( + defSite: Int, r: Result, reset: Boolean = false + ): Unit = appendToFpe2Sci( + defSite, + StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation, + reset + ) + + /** + * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] + * map accordingly, however, only if `defSite` is not yet present. + */ + def appendToFpe2Sci( + defSite: Int, sci: StringConstancyInformation, reset: Boolean = false + ): Unit = { + if (reset || !fpe2sci.contains(defSite)) { + fpe2sci(defSite) = ListBuffer() + } + fpe2sci(defSite).append(sci) + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 1e064149b1..7b44800c79 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -78,19 +78,16 @@ class InterproceduralStringAnalysis( dm.declaringClassType.toJava == data._2.classFile.thisType.toJava }.get - val tacai = ps(data._2, TACAI.key) - if (tacai.hasUBP) { - state.tac = tacai.ub.tac.get + val tacaiEOptP = ps(data._2, TACAI.key) + if (tacaiEOptP.hasUBP) { + state.tac = tacaiEOptP.ub.tac.get } else { - if (!state.dependees.contains(data)) { - state.dependees(data) = ListBuffer() - } - state.dependees(data).append(tacai) + state.dependees = tacaiEOptP :: state.dependees InterimResult( data, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, continuation(state) ) } @@ -100,15 +97,12 @@ class InterproceduralStringAnalysis( state.callees = calleesEOptP.ub determinePossibleStrings(state) } else { - if (!state.dependees.contains(data)) { - state.dependees(data) = ListBuffer() - } - state.dependees(data).append(calleesEOptP) + state.dependees = calleesEOptP :: state.dependees InterimResult( data, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, continuation(state) ) } @@ -157,13 +151,8 @@ class InterproceduralStringAnalysis( dependentVars.foreach { case (k, v) ⇒ state.var2IndexMapping(k) = v } val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { - case FinalP(p) ⇒ - return processFinalP(state.entity, state, ep.e, p) - case _ ⇒ - if (!state.dependees.contains(toAnalyze)) { - state.dependees(toAnalyze) = ListBuffer() - } - state.dependees(toAnalyze).append(ep) + case FinalP(p) ⇒ return processFinalP(state.entity, state, ep.e, p) + case _ ⇒ state.dependees = ep :: state.dependees } } } else { @@ -204,12 +193,12 @@ class InterproceduralStringAnalysis( // always be true (thus, the value of "sci" does not matter) } - if (state.dependees.values.nonEmpty) { + if (state.dependees.nonEmpty) { InterimResult( state.entity, StringConstancyProperty.ub, StringConstancyProperty.lb, - state.dependees.values.flatten, + state.dependees, continuation(state) ) } else { @@ -235,42 +224,40 @@ class InterproceduralStringAnalysis( case TACAI.key ⇒ eps match { case FinalP(tac: TACAI) ⇒ state.tac = tac.tac.get - state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) - if (state.dependees(inputData).isEmpty) { - state.dependees.remove(inputData) + state.dependees = state.dependees.filter(_.e != eps.e) + if (state.dependees.isEmpty) { determinePossibleStrings(state) } else { InterimResult( inputData, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, continuation(state) ) } case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees.values.flatten, continuation(state) + inputData, lb, ub, state.dependees, continuation(state) ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } case Callees.key ⇒ eps match { case FinalP(callees: Callees) ⇒ state.callees = callees - state.dependees(inputData) = state.dependees(inputData).filter(_.e != eps.e) - if (state.dependees(inputData).isEmpty) { - state.dependees.remove(inputData) + state.dependees = state.dependees.filter(_.e != eps.e) + if (state.dependees.isEmpty) { determinePossibleStrings(state) } else { InterimResult( inputData, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, continuation(state) ) } case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees.values.flatten, continuation(state) + inputData, lb, ub, state.dependees, continuation(state) ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } @@ -279,16 +266,10 @@ class InterproceduralStringAnalysis( case FinalP(p) ⇒ processFinalP(state.entity, state, eps.e, p) case InterimLUBP(lb, ub) ⇒ - for ((k, _) ← state.dependees) { - state.dependees(k) = state.dependees(k).filter(_.e != eps.e) - } - val eData = eps.e.asInstanceOf[P] - if (!state.dependees.contains(eData._2)) { - state.dependees(eData._2) = ListBuffer() - } - state.dependees(eData._2).append(eps) + state.dependees = state.dependees.filter(_.e != eps.e) + state.dependees = eps :: state.dependees InterimResult( - inputData, lb, ub, state.dependees.values.flatten, continuation(state) + inputData, lb, ub, state.dependees, continuation(state) ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } @@ -351,14 +332,8 @@ class InterproceduralStringAnalysis( state.appendToFpe2Sci(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) // No more dependees => Return the result for this analysis run - state.dependees.foreach { case (k, v) ⇒ state.dependees(k) = v.filter(_.e != e) } - state.dependees.foreach { - case (k, v) ⇒ if (v.isEmpty) { - state.dependees.remove(k) - } - } - val remDependees = state.dependees.values.flatten - if (remDependees.isEmpty) { + state.dependees = state.dependees.filter(_.e != e) + if (state.dependees.isEmpty) { val iHandler = InterproceduralInterpretationHandler( state.cfg, ps, declaredMethods, state, continuation(state) @@ -369,7 +344,7 @@ class InterproceduralStringAnalysis( data, StringConstancyProperty.ub, StringConstancyProperty.lb, - remDependees, + state.dependees, continuation(state) ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 1a451b895b..d3487e2883 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -64,10 +64,7 @@ abstract class AbstractStringInterpreter( if (tacai.hasUBP) { tacai.ub.tac } else { - if (!s.dependees.contains(m)) { - s.dependees(m) = ListBuffer() - } - s.dependees(m).append(tacai) + s.dependees = tacai :: s.dependees None } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index a69e2c13cd..70c1494081 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,8 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural -import scala.collection.mutable.ListBuffer - import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation @@ -65,16 +63,13 @@ class InterproceduralNonVirtualFunctionCallInterpreter( case FinalEP(e, p) ⇒ Result(e, p) case _ ⇒ - if (!state.dependees.contains(m)) { - state.dependees(m) = ListBuffer() - } - state.dependees(m).append(eps) + state.dependees = eps :: state.dependees state.var2IndexMapping(uvar) = defSite InterimResult( state.entity, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, c ) } @@ -84,7 +79,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( state.entity, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, c ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index fc037dfed0..96b7f07a88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -1,8 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural -import scala.collection.mutable.ListBuffer - import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation @@ -88,10 +86,7 @@ class InterproceduralStaticFunctionCallInterpreter( case FinalEP(e, p) ⇒ Result(e, p) case _ ⇒ - if (!state.dependees.contains(m)) { - state.dependees(m) = ListBuffer() - } - state.dependees(m).append(eps) + state.dependees = eps :: state.dependees state.var2IndexMapping(uvar) = defSite InterimResult( entity, @@ -107,7 +102,7 @@ class InterproceduralStaticFunctionCallInterpreter( m, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, c ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 631bfe758e..0fa99be29a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -1,8 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural -import scala.collection.mutable.ListBuffer - import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult @@ -141,10 +139,7 @@ class VirtualFunctionCallPreparationInterpreter( state.appendResultToFpe2Sci(defSite, r) r case _ ⇒ - if (!state.dependees.contains(nextMethod)) { - state.dependees(nextMethod) = ListBuffer() - } - state.dependees(nextMethod).append(eps) + state.dependees = eps :: state.dependees state.var2IndexMapping(uvar) = defSite InterimResult( entity, @@ -160,7 +155,7 @@ class VirtualFunctionCallPreparationInterpreter( nextMethod, StringConstancyProperty.lb, StringConstancyProperty.ub, - state.dependees.values.flatten, + state.dependees, c ) } From 3be8dbf268e86162267eebca4b6083cfc81da8b6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 14:55:27 +0100 Subject: [PATCH 178/316] Extended the analysis in the way that it evaluates parameters of the method under analysis and uses these string information later on. --- .../InterproceduralComputationState.scala | 11 +- .../InterproceduralStringAnalysis.scala | 158 ++++++++++++++---- 2 files changed, 136 insertions(+), 33 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 60a5085b03..3ce453034f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -11,6 +11,7 @@ import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.Stmt @@ -19,6 +20,7 @@ import org.opalj.tac.DUVar import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler /** * This class is to be used to store state information that are required at a later point in @@ -32,18 +34,23 @@ case class InterproceduralComputationState(entity: P) { var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ // The Control Flow Graph of the entity's method var cfg: CFG[Stmt[V], TACStmts[V]] = _ + // The interpretation handler to use + var iHandler: InterproceduralInterpretationHandler = _ // The computed lean path that corresponds to the given entity var computedLeanPath: Path = _ // Callees information regarding the declared method that corresponds to the entity's method var callees: Callees = _ + // Callers information regarding the declared method that corresponds to the entity's method + var callers: CallersProperty = _ // If not empty, this routine can only produce an intermediate result var dependees: List[EOptionP[Entity, Property]] = List() // A mapping from DUVar elements to the corresponding indices of the FlatPathElements val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() // A mapping from values / indices of FlatPathElements to StringConstancyInformation val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() - // Parameter values of method / function; a mapping from the definition sites of parameter ( - // negative values) to a correct index of `params` has to be made! + // Parameter values of a method / function. The structure of this field is as follows: Each item + // in the outer list holds the parameters of a concrete call. A mapping from the definition + // sites of parameter (negative values) to a correct index of `params` has to be made! var params: List[Seq[StringConstancyInformation]] = List() /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 7b44800c79..f57bc08ae7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -14,6 +14,7 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject @@ -21,22 +22,30 @@ import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.Method import org.opalj.tac.Stmt -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer -import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder +import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.ExprStmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath +import org.opalj.tac.Assignment +import org.opalj.tac.DUVar +import org.opalj.tac.FunctionCall +import org.opalj.tac.MethodCall +import org.opalj.tac.SimpleTACAIKey +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -116,22 +125,60 @@ class InterproceduralStringAnalysis( private def determinePossibleStrings( state: InterproceduralComputationState ): ProperPropertyComputationResult = { + val uvar = state.entity._1 + val defSites = uvar.definedBy.toArray.sorted + + val tacProvider = p.get(SimpleTACAIKey) + if (state.cfg == null) { + state.cfg = tacProvider(state.entity._2).cfg + } + if (state.iHandler == null) { + state.iHandler = InterproceduralInterpretationHandler( + state.cfg, ps, declaredMethods, state, continuation(state) + ) + } + + // In case a parameter is required for approximating a string, retrieve callers information + // (but only once) + val hasParamDefSite = defSites.exists(_ < 0) + if (hasParamDefSite && state.callers == null) { + val declaredMethods = project.get(DeclaredMethodsKey) + val dm = declaredMethods.declaredMethods.filter { dm ⇒ + dm.name == state.entity._2.name && + dm.declaringClassType.toJava == state.entity._2.classFile.thisType.toJava + }.next() + val callersEOptP = ps(dm, CallersProperty.key) + if (callersEOptP.hasUBP) { + state.callers = callersEOptP.ub + } else { + state.dependees = callersEOptP :: state.dependees + return InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) + } + } + if (hasParamDefSite) { + registerParams(state, tacProvider) + state.params = InterproceduralStringAnalysis.getParams(state.entity) + } else { + state.params = InterproceduralStringAnalysis.getParams(state.entity) + } + // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val tacProvider = p.get(SimpleTACAIKey) - state.cfg = tacProvider(state.entity._2).cfg - state.params = InterproceduralStringAnalysis.getParams(state.entity) val stmts = state.cfg.code.instructions - val uvar = state.entity._1 - val defSites = uvar.definedBy.toArray.sorted - // Function parameters are currently regarded as dynamic value; the following if finds read - // operations of strings (not String{Builder, Buffer}s, they will be handles further down + // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - return Result(state.entity, StringConstancyProperty.lb) + val r = state.iHandler.processDefSite(defSites.head, state.params) + return Result(state.entity, StringConstancyProperty.extractFromPPCR(r)) } - val pathFinder: AbstractPathFinder = new WindowPathFinder(state.cfg) + val pathFinder: AbstractPathFinder = new WindowPathFinder(state.cfg) val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) @@ -156,11 +203,9 @@ class InterproceduralStringAnalysis( } } } else { - val iHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, continuation(state) - ) - if (computeResultsForPath(state.computedLeanPath, iHandler, state)) { - sci = new PathTransformer(iHandler).pathToStringTree( + // TODO: Parameters can be removed + if (computeResultsForPath(state.computedLeanPath, state.iHandler, state)) { + sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) } @@ -180,11 +225,8 @@ class InterproceduralStringAnalysis( Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) } - val iHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, continuation(state) - ) - if (computeResultsForPath(state.computedLeanPath, iHandler, state)) { - sci = new PathTransformer(iHandler).pathToStringTree( + if (computeResultsForPath(state.computedLeanPath, state.iHandler, state)) { + sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) } @@ -261,6 +303,26 @@ class InterproceduralStringAnalysis( ) case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") } + case CallersProperty.key ⇒ eps match { + case FinalP(callers: CallersProperty) ⇒ + state.callers = callers + state.dependees = state.dependees.filter(_.e != eps.e) + if (state.dependees.isEmpty) { + determinePossibleStrings(state) + } else { + InterimResult( + inputData, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) + } + case InterimLUBP(lb, ub) ⇒ InterimResult( + inputData, lb, ub, state.dependees, continuation(state) + ) + case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") + } case StringConstancyProperty.key ⇒ eps match { case FinalP(p) ⇒ @@ -306,9 +368,8 @@ class InterproceduralStringAnalysis( private def computeFinalResult( data: P, state: InterproceduralComputationState, - iHandler: InterproceduralInterpretationHandler ): Result = { - finalizePreparations(state.computedLeanPath, state, iHandler) + finalizePreparations(state.computedLeanPath, state, state.iHandler) val finalSci = new PathTransformer(null).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap, resetExprHandler = false ).reduce(true) @@ -334,11 +395,7 @@ class InterproceduralStringAnalysis( // No more dependees => Return the result for this analysis run state.dependees = state.dependees.filter(_.e != e) if (state.dependees.isEmpty) { - val iHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, - continuation(state) - ) - computeFinalResult(data, state, iHandler) + computeFinalResult(data, state) } else { InterimResult( data, @@ -350,6 +407,45 @@ class InterproceduralStringAnalysis( } } + /** + * This method takes a computation state, `state` as well as a TAC provider, `tacProvider`, and + * determines the interpretations of all parameters of the method under analysis. These + * interpretations are registered using [[InterproceduralStringAnalysis.registerParams]]. + */ + private def registerParams( + state: InterproceduralComputationState, + tacProvider: Method ⇒ TACode[TACMethodParameter, DUVar[ValueInformation]] + ): Unit = { + val paramsSci = ListBuffer[ListBuffer[StringConstancyInformation]]() + state.callers.callers(declaredMethods).foreach { + case (m, pc) ⇒ + val tac = propertyStore(m.definedMethod, TACAI.key).ub.tac.get + paramsSci.append(ListBuffer()) + val params = tac.stmts(tac.pcToIndex(pc)) match { + case Assignment(_, _, fc: FunctionCall[V]) ⇒ fc.params + case Assignment(_, _, mc: MethodCall[V]) ⇒ mc.params + case ExprStmt(_, fc: FunctionCall[V]) ⇒ fc.params + case ExprStmt(_, fc: MethodCall[V]) ⇒ fc.params + case mc: MethodCall[V] ⇒ mc.params + case _ ⇒ List() + } + params.foreach { p ⇒ + val iHandler = InterproceduralInterpretationHandler( + tacProvider(m.definedMethod).cfg, + propertyStore, + declaredMethods, + state, + continuation(state) + ) + val prop = StringConstancyProperty.extractFromPPCR( + iHandler.processDefSite(p.asVar.definedBy.head) + ) + paramsSci.last.append(prop.stringConstancyInformation) + } + } + InterproceduralStringAnalysis.registerParams(state.entity, paramsSci.toList) + } + /** * This function traverses the given path, computes all string values along the path and stores * these information in the given state. @@ -534,7 +630,7 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule * Executor for the lazy analysis. */ object LazyInterproceduralStringAnalysis - extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData From 17cfa1e9453bf2c8784b202b46c405d5bba303b2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 15:13:58 +0100 Subject: [PATCH 179/316] Removed unnecessary arguments / parameters. --- .../InterproceduralStringAnalysis.scala | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index f57bc08ae7..527ff189c7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -198,13 +198,13 @@ class InterproceduralStringAnalysis( dependentVars.foreach { case (k, v) ⇒ state.var2IndexMapping(k) = v } val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { - case FinalP(p) ⇒ return processFinalP(state.entity, state, ep.e, p) + case FinalP(p) ⇒ return processFinalP(state, ep.e, p) case _ ⇒ state.dependees = ep :: state.dependees } } } else { // TODO: Parameters can be removed - if (computeResultsForPath(state.computedLeanPath, state.iHandler, state)) { + if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) @@ -225,7 +225,7 @@ class InterproceduralStringAnalysis( Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) } - if (computeResultsForPath(state.computedLeanPath, state.iHandler, state)) { + if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap ).reduce(true) @@ -326,7 +326,7 @@ class InterproceduralStringAnalysis( case StringConstancyProperty.key ⇒ eps match { case FinalP(p) ⇒ - processFinalP(state.entity, state, eps.e, p) + processFinalP(state, eps.e, p) case InterimLUBP(lb, ub) ⇒ state.dependees = state.dependees.filter(_.e != eps.e) state.dependees = eps :: state.dependees @@ -357,7 +357,6 @@ class InterproceduralStringAnalysis( * of instruction that could only be prepared (e.g., if an array load included a method call, * its final result is not yet ready, however, this function finalizes, e.g., that load). * - * @param data The entity that was to analyze. * @param state The final computation state. For this state the following criteria must apply: * For each [[FlatPathElement]], there must be a corresponding entry in * `state.fpe2sci`. If this criteria is not met, a [[NullPointerException]] will @@ -366,15 +365,14 @@ class InterproceduralStringAnalysis( * @return Returns the final result. */ private def computeFinalResult( - data: P, state: InterproceduralComputationState, ): Result = { finalizePreparations(state.computedLeanPath, state, state.iHandler) val finalSci = new PathTransformer(null).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap, resetExprHandler = false ).reduce(true) - InterproceduralStringAnalysis.unregisterParams(data) - Result(data, StringConstancyProperty(finalSci)) + InterproceduralStringAnalysis.unregisterParams(state.entity) + Result(state.entity, StringConstancyProperty(finalSci)) } /** @@ -382,7 +380,6 @@ class InterproceduralStringAnalysis( * [[org.opalj.fpcf.FinalP]]. */ private def processFinalP( - data: P, state: InterproceduralComputationState, e: Entity, p: Property @@ -395,10 +392,10 @@ class InterproceduralStringAnalysis( // No more dependees => Return the result for this analysis run state.dependees = state.dependees.filter(_.e != e) if (state.dependees.isEmpty) { - computeFinalResult(data, state) + computeFinalResult(state) } else { InterimResult( - data, + state.entity, StringConstancyProperty.ub, StringConstancyProperty.lb, state.dependees, @@ -451,14 +448,12 @@ class InterproceduralStringAnalysis( * these information in the given state. * * @param p The path to traverse. - * @param iHandler The handler for interpreting string related sites. * @param state The current state of the computation. This function will alter * [[InterproceduralComputationState.fpe2sci]]. * @return Returns `true` if all values computed for the path are final results. */ private def computeResultsForPath( p: Path, - iHandler: InterproceduralInterpretationHandler, state: InterproceduralComputationState ): Boolean = { var hasFinalResult = true @@ -466,14 +461,14 @@ class InterproceduralStringAnalysis( p.elements.foreach { case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { - iHandler.processDefSite(index, state.params) match { + state.iHandler.processDefSite(index, state.params) match { case r: Result ⇒ state.appendResultToFpe2Sci(index, r, reset = true) case _ ⇒ hasFinalResult = false } } case npe: NestedPathElement ⇒ val subFinalResult = computeResultsForPath( - Path(npe.element.toList), iHandler, state + Path(npe.element.toList), state ) if (hasFinalResult) { hasFinalResult = subFinalResult From f2a59d4ca1e6ee82e272cc6a18bce5d592378533 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:24:09 +0100 Subject: [PATCH 180/316] Fixed a little bug in the procedure that computes lean paths. --- .../tac/fpcf/analyses/string_analysis/preprocessing/Path.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala index 76512fe2e2..45d98401b8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/Path.scala @@ -276,7 +276,7 @@ case class Path(elements: List[SubPath]) { } }.map { s ⇒ (s, Unit) }.toMap var leanPath = ListBuffer[SubPath]() - val endSite = obj.definedBy.head + val endSite = obj.definedBy.toArray.max var reachedEndSite = false elements.foreach { next ⇒ From e1204dcfacb2e84d0d66084fbfb12dbea8c3e308 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:24:42 +0100 Subject: [PATCH 181/316] Fixed a little bug in the procedure that builds paths for "switch" control structures. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index b883422ae1..9c11af824d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -430,9 +430,12 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): (Path, List[(Int, Int)]) = { val startEndPairs = ListBuffer[(Int, Int)]() val switch = cfg.code.instructions(start).asSwitch - val caseStmts = switch.caseStmts.sorted + val caseStmts = ListBuffer[Int](switch.caseStmts.sorted: _*) val containsDefault = caseStmts.length == caseStmts.distinct.length + if (containsDefault) { + caseStmts.append(switch.defaultStmt) + } val pathType = if (containsDefault) NestedPathType.CondWithAlternative else NestedPathType.CondWithoutAlternative From 4b0cd7738e4a4a8619446ca5bd9835aee91b98aa Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:24:59 +0100 Subject: [PATCH 182/316] Removed a TODO comment. --- .../analyses/string_analysis/InterproceduralStringAnalysis.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 527ff189c7..38132124fb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -203,7 +203,6 @@ class InterproceduralStringAnalysis( } } } else { - // TODO: Parameters can be removed if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap From 1979acfca3fbd81be99190ae0cd8b8baf9465c3a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 16:42:01 +0100 Subject: [PATCH 183/316] Allow the repetitive evaluation of constant expressions. --- .../InterproceduralInterpretationHandler.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 53f5bb60ff..eb99cdfb42 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -84,6 +84,7 @@ class InterproceduralInterpretationHandler( } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } + // Note that def sites referring to constant expressions will be deleted further down processedDefSites.append(defSite) val callees = state.callees @@ -91,18 +92,22 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: StringConst) ⇒ val result = new StringConstInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + processedDefSites.remove(processedDefSites.length - 1) result case Assignment(_, _, expr: IntConst) ⇒ val result = new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + processedDefSites.remove(processedDefSites.length - 1) result case Assignment(_, _, expr: FloatConst) ⇒ val result = new FloatValueInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + processedDefSites.remove(processedDefSites.length - 1) result case Assignment(_, _, expr: DoubleConst) ⇒ val result = new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + processedDefSites.remove(processedDefSites.length - 1) result case Assignment(_, _, expr: ArrayLoad[V]) ⇒ new ArrayPreparationInterpreter(cfg, this, state, params).interpret(expr, defSite) From 438e1f69d36335b59be754d9b3d516cb4e02bf13 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 19:19:20 +0100 Subject: [PATCH 184/316] Slightly changed the "isStringBuilderBufferToStringCall" function (it did not work when a class was not available, i.e., ClassNotFoundException). --- .../interpretation/InterpretationHandler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index c7f1d95ebb..7b9d6817f6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -82,8 +82,8 @@ object InterpretationHandler { def isStringBuilderBufferToStringCall(expr: Expr[V]): Boolean = expr match { case VirtualFunctionCall(_, clazz, _, name, _, _, _) ⇒ - val className = clazz.toJavaClass.getName - (className == "java.lang.StringBuilder" || className == "java.lang.StringBuffer") && + val className = clazz.mostPreciseObjectType.fqn + (className == "java/lang/StringBuilder" || className == "java/lang/StringBuffer") && name == "toString" case _ ⇒ false } From a8fe58869c7a018c9d5bad49b813ef9440434906 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 19 Feb 2019 19:36:49 +0100 Subject: [PATCH 185/316] Extended the ArrayPreparationInterpreter to capture a result in case it can be computed. --- .../interprocedural/ArrayPreparationInterpreter.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 6296a31f6a..a0d8fd5db0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -78,7 +78,13 @@ class ArrayPreparationInterpreter( if (interimResult.isDefined) { interimResult.get } else { - results.head + val scis = results.map(StringConstancyProperty.extractFromPPCR) + val resultSci = StringConstancyInformation.reduceMultiple( + scis.map(_.stringConstancyInformation) + ) + val result = Result(instr, StringConstancyProperty(resultSci)) + state.appendResultToFpe2Sci(defSite, result) + result } } From 01c3529630cb50633664650410fbf825fb2099b9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 20 Feb 2019 17:31:06 +0100 Subject: [PATCH 186/316] Slightly changed the parameter registration and fixed a bug in the registerParams method. --- .../InterproceduralStringAnalysis.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 38132124fb..56ae35d8e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -140,8 +140,8 @@ class InterproceduralStringAnalysis( // In case a parameter is required for approximating a string, retrieve callers information // (but only once) - val hasParamDefSite = defSites.exists(_ < 0) - if (hasParamDefSite && state.callers == null) { + state.params = InterproceduralStringAnalysis.getParams(state.entity) + if (state.callers == null && state.params.isEmpty) { val declaredMethods = project.get(DeclaredMethodsKey) val dm = declaredMethods.declaredMethods.filter { dm ⇒ dm.name == state.entity._2.name && @@ -150,6 +150,7 @@ class InterproceduralStringAnalysis( val callersEOptP = ps(dm, CallersProperty.key) if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub + registerParams(state, tacProvider) } else { state.dependees = callersEOptP :: state.dependees return InterimResult( @@ -161,12 +162,7 @@ class InterproceduralStringAnalysis( ) } } - if (hasParamDefSite) { - registerParams(state, tacProvider) - state.params = InterproceduralStringAnalysis.getParams(state.entity) - } else { - state.params = InterproceduralStringAnalysis.getParams(state.entity) - } + state.params = InterproceduralStringAnalysis.getParams(state.entity) // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation @@ -307,6 +303,7 @@ class InterproceduralStringAnalysis( state.callers = callers state.dependees = state.dependees.filter(_.e != eps.e) if (state.dependees.isEmpty) { + registerParams(state, p.get(SimpleTACAIKey)) determinePossibleStrings(state) } else { InterimResult( @@ -433,9 +430,13 @@ class InterproceduralStringAnalysis( state, continuation(state) ) + val defSite = p.asVar.definedBy.head val prop = StringConstancyProperty.extractFromPPCR( - iHandler.processDefSite(p.asVar.definedBy.head) + iHandler.processDefSite(defSite) ) + // We have to remove the element (it was added during the processDefSite call) + // as otherwise false information might be stored and used + state.fpe2sci.remove(defSite) paramsSci.last.append(prop.stringConstancyInformation) } } From 221404577cfdc5f54be22b66a9e0316568e33d91 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 22 Feb 2019 17:15:22 +0100 Subject: [PATCH 187/316] Added primitive support for GetStatic expressions (needs to be enhanced later on). --- .../InterproceduralTestMethods.java | 17 +++++++++++++++++ .../InterproceduralGetStaticInterpreter.scala | 6 +++--- .../InterproceduralInterpretationHandler.scala | 5 +++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 2c6a81afac..af5e90e1fb 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -6,6 +6,7 @@ import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; +import javax.management.remote.rmi.RMIServer; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; @@ -21,6 +22,8 @@ public class InterproceduralTestMethods { public static final String JAVA_LANG = "java.lang"; + private static final String rmiServerImplStubClassName = + RMIServer.class.getName() + "Impl_Stub"; /** * {@see LocalTestMethods#analyzeString} @@ -240,6 +243,20 @@ public void contextInsensitivityTest() { analyzeString(sb.toString()); } + @StringDefinitionsCollection( + value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " + + "is involved", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" + ) + + }) + public void getStaticTest() { + analyzeString(rmiServerImplStubClassName); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala index e0fd3216f8..11104b9b45 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala @@ -10,7 +10,6 @@ import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler /** * The `InterproceduralGetStaticInterpreter` is responsible for processing @@ -22,7 +21,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedura */ class InterproceduralGetStaticInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: IntraproceduralInterpretationHandler + exprHandler: InterproceduralInterpretationHandler ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = GetStatic @@ -31,11 +30,12 @@ class InterproceduralGetStaticInterpreter( * Currently, this type is not interpreted. Thus, this function always returns a result * containing [[StringConstancyProperty.lb]]. * - * @note For this implementation, `defSite` plays a role! + * @note For this implementation, `defSite` does currently not play a role! * * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = + // TODO: How can they be better approximated? Result(instr, StringConstancyProperty.lb) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index eb99cdfb42..fd2bd58bb5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -38,6 +38,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringC import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.VirtualFunctionCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.GetStatic /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -115,6 +116,10 @@ class InterproceduralInterpretationHandler( val result = new NewInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) result + case Assignment(_, _, expr: GetStatic) ⇒ + new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) + case ExprStmt(_, expr: GetStatic) ⇒ + new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c From 1c50c9cb30ec455aab45e279003afaa7c0fbac61 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 22 Feb 2019 17:15:48 +0100 Subject: [PATCH 188/316] Correctly handle the case if no TAC is available. --- .../string_analysis/InterproceduralStringAnalysis.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 56ae35d8e8..7c94c1098d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -89,7 +89,12 @@ class InterproceduralStringAnalysis( val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { - state.tac = tacaiEOptP.ub.tac.get + if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + return Result(state.entity, StringConstancyProperty.lb) + } else { + state.tac = tacaiEOptP.ub.tac.get + } } else { state.dependees = tacaiEOptP :: state.dependees InterimResult( From 9d18839300bd231499fbb42f12cae9c66d30da91 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 23 Feb 2019 14:26:22 +0100 Subject: [PATCH 189/316] Minor changes. --- .../string_analysis/InterproceduralStringAnalysis.scala | 2 +- .../InterproceduralNonVirtualFunctionCallInterpreter.scala | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 7c94c1098d..f1610e55ef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -429,7 +429,7 @@ class InterproceduralStringAnalysis( } params.foreach { p ⇒ val iHandler = InterproceduralInterpretationHandler( - tacProvider(m.definedMethod).cfg, + tac.cfg, propertyStore, declaredMethods, state, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 70c1494081..39af2ded50 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -50,7 +50,12 @@ class InterproceduralNonVirtualFunctionCallInterpreter( */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val methods = getMethodsForPC(instr.pc, ps, state.callees, declaredMethods) + if (methods._1.isEmpty) { + // No methods available => Return lower bound + return Result(instr, StringConstancyProperty.lb) + } val m = methods._1.head + val tac = getTACAI(ps, m, state) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis From 4706babdf3c7ea71d8ffb5a9e166bb26f773b285 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 11:15:07 +0100 Subject: [PATCH 190/316] Added handling for the implicit "this" parameter. --- .../InterproceduralInterpretationHandler.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index fd2bd58bb5..b12e66d2f3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -74,8 +74,9 @@ class InterproceduralInterpretationHandler( // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt - // Function parameters are not evaluated when none are present - if (defSite < 0 && params.isEmpty) { + // Function parameters are not evaluated when none are present (this always includes the + // implicit parameter for "this") + if (defSite < 0 && (params.isEmpty || defSite == -1)) { return Result(e, StringConstancyProperty.lb) } else if (defSite < 0) { val paramPos = Math.abs(defSite + 2) From c2cc00b37d282eb504794a576129958971e55d1a Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 11:45:41 +0100 Subject: [PATCH 191/316] Added support for an "append" argument which > 1 definition site. --- .../InterproceduralTestMethods.java | 19 ++++++++++++ ...alFunctionCallPreparationInterpreter.scala | 31 ++++++++++--------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index af5e90e1fb..476538d7a2 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -257,6 +257,25 @@ public void getStaticTest() { analyzeString(rmiServerImplStubClassName); } + @StringDefinitionsCollection( + value = "a case where the append value has more than one def site", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "It is (great|not great)" + ) + + }) + public void appendWithTwoDefSites(int i) { + String s; + if (i > 0) { + s = "great"; + } else { + s = "not great"; + } + analyzeString(new StringBuilder("It is ").append(s).toString()); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 0fa99be29a..ea5f09be74 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -263,29 +263,32 @@ class VirtualFunctionCallPreparationInterpreter( private def valueOfAppendCall( call: VirtualFunctionCall[V], state: InterproceduralComputationState ): ProperPropertyComputationResult = { - val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append - val defSiteHead = param.definedBy.head - var value = exprHandler.processDefSite(defSiteHead, params) + val param = call.params.head.asVar + val defSites = param.definedBy.toArray.sorted + val values = defSites.map(exprHandler.processDefSite(_, params)) - // Defer the computation if there is no final result (yet) - if (!value.isInstanceOf[Result]) { - return value + // Defer the computation if there is at least one intermediate result + if (!values.forall(_.isInstanceOf[Result])) { + return values.find(!_.isInstanceOf[Result]).get } - var valueSci = StringConstancyProperty.extractFromPPCR(value).stringConstancyInformation + var valueSci = StringConstancyInformation.reduceMultiple(values.map { + StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation + }) // If defSiteHead points to a "New", value will be the empty list. In that case, process // the first use site (which is the call) if (valueSci.isTheNeutralElement) { - val ds = cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min - value = exprHandler.processDefSite(ds, params) + val ds = cfg.code.instructions(defSites.head).asAssignment.targetVar.usedBy.toArray.min + val r = exprHandler.processDefSite(ds, params) // Again, defer the computation if there is no final result (yet) - if (!value.isInstanceOf[Result]) { - return value + if (!r.isInstanceOf[Result]) { + return r + } else { + valueSci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation } } - valueSci = StringConstancyProperty.extractFromPPCR(value).stringConstancyInformation val finalSci = param.value.computationalType match { // For some types, we know the (dynamic) values case ComputationalTypeInt ⇒ @@ -306,8 +309,8 @@ class VirtualFunctionCallPreparationInterpreter( valueSci } - state.appendToFpe2Sci(defSiteHead, valueSci, reset = true) - val e: Integer = defSiteHead + val e: Integer = defSites.head + state.appendToFpe2Sci(e, valueSci, reset = true) Result(e, StringConstancyProperty(finalSci)) } From 8f6b610e3b37b9291c11751d4512b62ab87e0f91 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 17:20:13 +0100 Subject: [PATCH 192/316] Added support for functions as arguments to (other) functions. --- .../InterproceduralTestMethods.java | 34 +++++ .../InterproceduralComputationState.scala | 20 ++- .../InterproceduralStringAnalysis.scala | 142 +++++++++++++----- .../AbstractStringInterpreter.scala | 4 +- 4 files changed, 162 insertions(+), 38 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 476538d7a2..36dc46fece 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -276,6 +276,36 @@ public void appendWithTwoDefSites(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } + @StringDefinitionsCollection( + value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " + + "is involved", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello, World" + ) + + }) + public String callerWithFunctionParameterTest(String s, float i) { + analyzeString(s); + return s; + } + + /** + * Necessary for the callerWithFunctionParameterTest. + */ + public void belongsToSomeTestCase() { + String s = callerWithFunctionParameterTest(belongsToTheSameTestCase(), 900); + System.out.println(s); + } + + /** + * Necessary for the callerWithFunctionParameterTest. + */ + public static String belongsToTheSameTestCase() { + return getHelloWorld(); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } @@ -288,4 +318,8 @@ private String getSimpleStringBuilderClassName() { return "StringBuilder"; } + private static String getHelloWorld() { + return "Hello, World"; + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 3ce453034f..1b07eabe96 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -48,10 +48,28 @@ case class InterproceduralComputationState(entity: P) { val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() // A mapping from values / indices of FlatPathElements to StringConstancyInformation val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() + /** + * An analysis may depend on the evaluation of its parameters. This number indicates how many + * of such dependencies are still to be computed. + */ + var parameterDependeesCount = 0 + /** + * Indicates whether the basic setup of the string analysis is done. This value is to be set to + * `true`, when all necessary dependees and parameters are available. + */ + var isSetupCompleted = false + /** + * It might be that the result of parameters, which have to be evaluated, is not available right + * away. Later on, when the result is available, it is necessary to map it to the right + * position; this map stores this information. The key is the entity, with which the String + * Analysis was started recursively; the value is a pair where the first value indicates the + * index of the method and the second value the position of the parameter. + */ + val paramResultPositions: mutable.Map[P, (Int, Int)] = mutable.Map() // Parameter values of a method / function. The structure of this field is as follows: Each item // in the outer list holds the parameters of a concrete call. A mapping from the definition // sites of parameter (negative values) to a correct index of `params` has to be made! - var params: List[Seq[StringConstancyInformation]] = List() + var params: ListBuffer[ListBuffer[StringConstancyInformation]] = ListBuffer() /** * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index f1610e55ef..b0cf71c800 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -145,8 +145,11 @@ class InterproceduralStringAnalysis( // In case a parameter is required for approximating a string, retrieve callers information // (but only once) - state.params = InterproceduralStringAnalysis.getParams(state.entity) - if (state.callers == null && state.params.isEmpty) { + if (state.params.isEmpty) { + state.params = InterproceduralStringAnalysis.getParams(state.entity) + } + if (state.entity._2.parameterTypes.length > 0 && + state.callers == null && state.params.isEmpty) { val declaredMethods = project.get(DeclaredMethodsKey) val dm = declaredMethods.declaredMethods.filter { dm ⇒ dm.name == state.entity._2.name && @@ -155,7 +158,15 @@ class InterproceduralStringAnalysis( val callersEOptP = ps(dm, CallersProperty.key) if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub - registerParams(state, tacProvider) + if (!registerParams(state, tacProvider)) { + return InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) + } } else { state.dependees = callersEOptP :: state.dependees return InterimResult( @@ -167,7 +178,18 @@ class InterproceduralStringAnalysis( ) } } - state.params = InterproceduralStringAnalysis.getParams(state.entity) + + if (state.parameterDependeesCount > 0) { + return InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) + } else { + state.isSetupCompleted = true + } // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation @@ -175,7 +197,8 @@ class InterproceduralStringAnalysis( // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - val r = state.iHandler.processDefSite(defSites.head, state.params) + state.computedLeanPath = Path(List(FlatPathElement(defSites.head))) + val r = state.iHandler.processDefSite(defSites.head, state.params.toList) return Result(state.entity, StringConstancyProperty.extractFromPPCR(r)) } @@ -326,8 +349,22 @@ class InterproceduralStringAnalysis( } case StringConstancyProperty.key ⇒ eps match { - case FinalP(p) ⇒ - processFinalP(state, eps.e, p) + case FinalP(p: StringConstancyProperty) ⇒ + val resultEntity = eps.e.asInstanceOf[P] + // If necessary, update parameter information + if (state.paramResultPositions.contains(resultEntity)) { + val pos = state.paramResultPositions(resultEntity) + state.params(pos._1)(pos._2) = p.stringConstancyInformation + state.paramResultPositions.remove(resultEntity) + state.parameterDependeesCount -= 1 + state.dependees = state.dependees.filter(_.e != eps.e) + } + + if (state.isSetupCompleted && state.parameterDependeesCount == 0) { + processFinalP(state, eps.e, p) + } else { + determinePossibleStrings(state) + } case InterimLUBP(lb, ub) ⇒ state.dependees = state.dependees.filter(_.e != eps.e) state.dependees = eps :: state.dependees @@ -413,12 +450,11 @@ class InterproceduralStringAnalysis( private def registerParams( state: InterproceduralComputationState, tacProvider: Method ⇒ TACode[TACMethodParameter, DUVar[ValueInformation]] - ): Unit = { - val paramsSci = ListBuffer[ListBuffer[StringConstancyInformation]]() - state.callers.callers(declaredMethods).foreach { - case (m, pc) ⇒ + ): Boolean = { + var hasIntermediateResult = false + state.callers.callers(declaredMethods).toSeq.zipWithIndex.foreach { + case ((m, pc), methodIndex) ⇒ val tac = propertyStore(m.definedMethod, TACAI.key).ub.tac.get - paramsSci.append(ListBuffer()) val params = tac.stmts(tac.pcToIndex(pc)) match { case Assignment(_, _, fc: FunctionCall[V]) ⇒ fc.params case Assignment(_, _, mc: MethodCall[V]) ⇒ mc.params @@ -427,25 +463,40 @@ class InterproceduralStringAnalysis( case mc: MethodCall[V] ⇒ mc.params case _ ⇒ List() } - params.foreach { p ⇒ - val iHandler = InterproceduralInterpretationHandler( - tac.cfg, - propertyStore, - declaredMethods, - state, - continuation(state) - ) - val defSite = p.asVar.definedBy.head - val prop = StringConstancyProperty.extractFromPPCR( - iHandler.processDefSite(defSite) - ) - // We have to remove the element (it was added during the processDefSite call) - // as otherwise false information might be stored and used - state.fpe2sci.remove(defSite) - paramsSci.last.append(prop.stringConstancyInformation) + params.zipWithIndex.foreach { case (p, paramIndex) ⇒ + // Add an element to the params list (we do it here because we know how many + // parameters there are) + if (state.params.length <= methodIndex) { + state.params.append(params.indices.map(_ ⇒ + StringConstancyInformation.getNeutralElement + ).to[ListBuffer]) + } + // Recursively analyze supported types + if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { + val paramEntity = (p.asVar, m.definedMethod) + val eps = propertyStore(paramEntity, StringConstancyProperty.key) + state.var2IndexMapping(paramEntity._1) = paramIndex + eps match { + case FinalP(r) ⇒ + state.params(methodIndex)(paramIndex) = r.stringConstancyInformation + case _ ⇒ + state.dependees = eps :: state.dependees + hasIntermediateResult = true + state.paramResultPositions(paramEntity) = (methodIndex, paramIndex) + state.parameterDependeesCount += 1 + } + } else { + state.params(methodIndex)(paramIndex) = + StringConstancyProperty.lb.stringConstancyInformation + } + } } - InterproceduralStringAnalysis.registerParams(state.entity, paramsSci.toList) + // If all parameters could already be determined, register them + if (!hasIntermediateResult) { + InterproceduralStringAnalysis.registerParams(state.entity, state.params) + } + !hasIntermediateResult } /** @@ -466,7 +517,7 @@ class InterproceduralStringAnalysis( p.elements.foreach { case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { - state.iHandler.processDefSite(index, state.params) match { + state.iHandler.processDefSite(index, state.params.toList) match { case r: Result ⇒ state.appendResultToFpe2Sci(index, r, reset = true) case _ ⇒ hasFinalResult = false } @@ -578,9 +629,9 @@ object InterproceduralStringAnalysis { * insensitive, we have a list of lists to capture all parameters of all potential method / * function calls. */ - private val paramInfos = mutable.Map[Entity, ListBuffer[Seq[StringConstancyInformation]]]() + private val paramInfos = mutable.Map[Entity, ListBuffer[ListBuffer[StringConstancyInformation]]]() - def registerParams(e: Entity, scis: List[Seq[StringConstancyInformation]]): Unit = { + def registerParams(e: Entity, scis: ListBuffer[ListBuffer[StringConstancyInformation]]): Unit = { if (!paramInfos.contains(e)) { paramInfos(e) = ListBuffer(scis: _*) } else { @@ -590,11 +641,32 @@ object InterproceduralStringAnalysis { def unregisterParams(e: Entity): Unit = paramInfos.remove(e) - def getParams(e: Entity): List[Seq[StringConstancyInformation]] = + def getParams(e: Entity): ListBuffer[ListBuffer[StringConstancyInformation]] = if (paramInfos.contains(e)) { - paramInfos(e).toList + paramInfos(e) + } else { + ListBuffer() + } + + /** + * Checks whether a value of some type is supported by the [[InterproceduralStringAnalysis]]. + * Currently supported types are, java.lang.String and the primitive types short, int, float, + * and double. + * + * @param v The value to check if it is supported. + * @return Returns `true` if `v` is a supported type and `false` otherwise. + */ + def isSupportedType(v: V): Boolean = + if (v.value.isPrimitiveValue) { + val primTypeName = v.value.asPrimitiveValue.primitiveType.toJava + primTypeName == "short" || primTypeName == "int" || primTypeName == "float" || + primTypeName == "double" } else { - List() + try { + v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava == "java.lang.String" + } catch { + case _: Exception ⇒ false + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index d3487e2883..f3ceda2b60 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -119,13 +119,13 @@ abstract class AbstractStringInterpreter( protected def evaluateParameters( params: List[Seq[Expr[V]]], iHandler: InterproceduralInterpretationHandler - ): List[Seq[StringConstancyInformation]] = params.map(_.map { expr ⇒ + ): ListBuffer[ListBuffer[StringConstancyInformation]] = params.map(_.map { expr ⇒ val scis = expr.asVar.definedBy.map(iHandler.processDefSite(_, List())).map { r ⇒ // TODO: Current assumption: Results of parameters are available right away StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation } StringConstancyInformation.reduceMultiple(scis) - }) + }.to[ListBuffer]).to[ListBuffer] /** * From d345766d5b971db2b731964c8824aae4e45701db Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 19:47:25 +0100 Subject: [PATCH 193/316] Provide a default value if a path could only be reduced to the neutral element. --- .../string_analysis/preprocessing/PathTransformer.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index cd9dbb7bdb..f3016310ce 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -128,7 +128,12 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { resetExprHandler: Boolean = true ): StringTree = { val tree = path.elements.size match { - case 1 ⇒ pathToTreeAcc(path.elements.head, fpe2Sci).get + case 1 ⇒ + // It might be that for some expressions, a neutral element is produced which is + // filtered out by pathToTreeAcc; return the lower bound in such cases + pathToTreeAcc(path.elements.head, fpe2Sci).getOrElse( + StringTreeConst(StringConstancyProperty.lb.stringConstancyInformation) + ) case _ ⇒ val concatElement = StringTreeConcat( path.elements.map { ne ⇒ From 28d383e22658dec9453e58ddab627e630e02e6ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 26 Feb 2019 20:25:39 +0100 Subject: [PATCH 194/316] The "append" finalizer did not correctly finalize when multiple definition sites of the "append" parameter were present. --- .../InterproceduralTestMethods.java | 20 +++++++++++++++++++ .../VirtualFunctionCallFinalizer.scala | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 36dc46fece..4c99236688 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -276,6 +276,26 @@ public void appendWithTwoDefSites(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } + @StringDefinitionsCollection( + value = "a case where the append value has more than one def site with a function " + + "call involved", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "It is (great|Hello, World)" + ) + + }) + public void appendWithTwoDefSitesWithFuncCallTest(int i) { + String s; + if (i > 0) { + s = "great"; + } else { + s = getHelloWorld(); + } + analyzeString(new StringBuilder("It is ").append(s).toString()); + } + @StringDefinitionsCollection( value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " + "is involved", diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 5f94f2f0e8..6a0b76f367 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -42,7 +42,7 @@ class VirtualFunctionCallFinalizer( instr.receiver.asVar.definedBy.toArray.sorted.flatMap(state.fpe2sci(_)) ) val appendSci = StringConstancyInformation.reduceMultiple( - state.fpe2sci(instr.params.head.asVar.definedBy.head) + instr.params.head.asVar.definedBy.toArray.sorted.flatMap { state.fpe2sci(_) } ) val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { From f71f9fabb537805b810f23cc5d64ec6a42bdaaa7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 08:00:46 +0100 Subject: [PATCH 195/316] Finalizers might have dependencies among each other as well. These are now regarded (and a test case was added). --- .../InterproceduralTestMethods.java | 17 ++++++++ .../VirtualFunctionCallFinalizer.scala | 43 +++++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 4c99236688..42c4ca5c2a 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -296,6 +296,23 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { analyzeString(new StringBuilder("It is ").append(s).toString()); } + @StringDefinitionsCollection( + value = "a case taken from com.sun.javafx.property.PropertyReference#reflect where " + + "a dependency within the finalize procedure is present", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "get(\\w|Hello, Worldjava.lang.Runtime)" + ) + + }) + public void dependenciesWithinFinalizeTest(String s) { + String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : + getHelloWorld() + getRuntimeClassName(); + String getterName = "get" + properName; + analyzeString(getterName); + } + @StringDefinitionsCollection( value = "a case taken from javax.management.remote.rmi.RMIConnector where a GetStatic " + "is involved", diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 6a0b76f367..603c342432 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -27,8 +27,10 @@ class VirtualFunctionCallFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - if (instr.name == "append") { - finalizeAppend(instr, defSite) + instr.name match { + case "append" ⇒ finalizeAppend(instr, defSite) + case "toString" ⇒ finalizeToString(instr, defSite) + case _ ⇒ } } @@ -38,11 +40,31 @@ class VirtualFunctionCallFinalizer( * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.VirtualFunctionCallPreparationInterpreter]]. */ private def finalizeAppend(instr: T, defSite: Int): Unit = { + val receiverDefSites = instr.receiver.asVar.definedBy.toArray.sorted + receiverDefSites.foreach { ds ⇒ + if (!state.fpe2sci.contains(ds)) { + state.iHandler.finalizeDefSite(ds, state) + } + } val receiverSci = StringConstancyInformation.reduceMultiple( - instr.receiver.asVar.definedBy.toArray.sorted.flatMap(state.fpe2sci(_)) + receiverDefSites.flatMap { s ⇒ + // As the receiver value is used already here, we do not want it to be used a + // second time (during the final traversing of the path); thus, reset it to have it + // only once in the result, i.e., final tree + val sci = state.fpe2sci(s) + state.appendToFpe2Sci(s, StringConstancyInformation.getNeutralElement, reset = true) + sci + } ) + + val paramDefSites = instr.params.head.asVar.definedBy.toArray.sorted + paramDefSites.foreach { ds ⇒ + if (!state.fpe2sci.contains(ds)) { + state.iHandler.finalizeDefSite(ds, state) + } + } val appendSci = StringConstancyInformation.reduceMultiple( - instr.params.head.asVar.definedBy.toArray.sorted.flatMap { state.fpe2sci(_) } + paramDefSites.flatMap(state.fpe2sci(_)) ) val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { @@ -64,4 +86,17 @@ class VirtualFunctionCallFinalizer( state.appendToFpe2Sci(defSite, finalSci, reset = true) } + private def finalizeToString(instr: T, defSite: Int): Unit = { + val dependeeSites = instr.receiver.asVar.definedBy + dependeeSites.foreach { nextDependeeSite ⇒ + if (!state.fpe2sci.contains(nextDependeeSite)) { + state.iHandler.finalizeDefSite(nextDependeeSite, state) + } + } + val finalSci = StringConstancyInformation.reduceMultiple( + dependeeSites.toArray.flatMap { ds ⇒ state.fpe2sci(ds) } + ) + state.appendToFpe2Sci(defSite, finalSci) + } + } From 4b6ab1c965cb94b208b8584b7a8ef88dc47c25c7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 11:54:30 +0100 Subject: [PATCH 196/316] Refined the procedure for finding the correct method for which to get callers information for. --- .../InterproceduralStringAnalysis.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index b0cf71c800..dd03b0c3cc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -150,11 +150,15 @@ class InterproceduralStringAnalysis( } if (state.entity._2.parameterTypes.length > 0 && state.callers == null && state.params.isEmpty) { - val declaredMethods = project.get(DeclaredMethodsKey) - val dm = declaredMethods.declaredMethods.filter { dm ⇒ + val declaredMethods = project.get(DeclaredMethodsKey).declaredMethods + val dm = declaredMethods.find { dm ⇒ + // TODO: What is a better / more robust way to compare methods (a check with == did + // not produce the expected behavior)? dm.name == state.entity._2.name && - dm.declaringClassType.toJava == state.entity._2.classFile.thisType.toJava - }.next() + dm.declaringClassType.toJava == state.entity._2.classFile.thisType.toJava && + dm.definedMethod.descriptor.parameterTypes.length == + state.entity._2.descriptor.parameterTypes.length + }.get val callersEOptP = ps(dm, CallersProperty.key) if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub From cc020df3a649dd89e5042288de9b0e69dbac3a4a Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 16:25:40 +0100 Subject: [PATCH 197/316] Converted some comments to doc strings. --- .../InterproceduralComputationState.scala | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 1b07eabe96..fff2183fd4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -30,23 +30,41 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedura * @param entity The entity for which the analysis was started with. */ case class InterproceduralComputationState(entity: P) { - // The Three-Address Code of the entity's method + /** + * The Three-Address Code of the entity's method + */ var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ - // The Control Flow Graph of the entity's method + /** + * The Control Flow Graph of the entity's method + */ var cfg: CFG[Stmt[V], TACStmts[V]] = _ - // The interpretation handler to use + /** + * The interpretation handler to use + */ var iHandler: InterproceduralInterpretationHandler = _ - // The computed lean path that corresponds to the given entity + /** + * The computed lean path that corresponds to the given entity + */ var computedLeanPath: Path = _ - // Callees information regarding the declared method that corresponds to the entity's method + /** + * Callees information regarding the declared method that corresponds to the entity's method + */ var callees: Callees = _ - // Callers information regarding the declared method that corresponds to the entity's method + /** + * Callers information regarding the declared method that corresponds to the entity's method + */ var callers: CallersProperty = _ - // If not empty, this routine can only produce an intermediate result + /** + * If not empty, this routine can only produce an intermediate result + */ var dependees: List[EOptionP[Entity, Property]] = List() - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + /** + * A mapping from DUVar elements to the corresponding indices of the FlatPathElements + */ val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() - // A mapping from values / indices of FlatPathElements to StringConstancyInformation + /** + * A mapping from values / indices of FlatPathElements to StringConstancyInformation + */ val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() /** * An analysis may depend on the evaluation of its parameters. This number indicates how many From 46beee4e9f6ed66aa01972dad3e4ceb4ad4876af Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Feb 2019 22:09:55 +0100 Subject: [PATCH 198/316] Added support for the interpretation of functions which have other functions as parameters. --- .../InterproceduralTestMethods.java | 25 ++++++ .../InterproceduralComputationState.scala | 7 ++ .../InterproceduralStringAnalysis.scala | 25 +++++- .../AbstractStringInterpreter.scala | 76 ++++++++++++++++--- .../InterpretationHandler.scala | 2 +- ...InterproceduralInterpretationHandler.scala | 36 ++++++++- ...ceduralStaticFunctionCallInterpreter.scala | 26 ++++++- ...alFunctionCallPreparationInterpreter.scala | 26 ++++++- .../string_analysis/string_analysis.scala | 8 ++ 9 files changed, 210 insertions(+), 21 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 42c4ca5c2a..5c978f1f24 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -328,6 +328,23 @@ public String callerWithFunctionParameterTest(String s, float i) { return s; } + @StringDefinitionsCollection( + value = "a case where a function takes another function as one of its parameters", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello, World!" + ), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello, World?" + ) + }) + public void functionWithFunctionParameter() { + analyzeString(addExclamationMark(getHelloWorld())); + analyzeString(addQuestionMark(getHelloWorld())); + } + /** * Necessary for the callerWithFunctionParameterTest. */ @@ -359,4 +376,12 @@ private static String getHelloWorld() { return "Hello, World"; } + private static String addExclamationMark(String s) { + return s + "!"; + } + + private String addQuestionMark(String s) { + return s + "?"; + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index fff2183fd4..61d3198f11 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -21,6 +21,7 @@ import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler +import org.opalj.tac.FunctionCall /** * This class is to be used to store state information that are required at a later point in @@ -89,6 +90,12 @@ case class InterproceduralComputationState(entity: P) { // sites of parameter (negative values) to a correct index of `params` has to be made! var params: ListBuffer[ListBuffer[StringConstancyInformation]] = ListBuffer() + val nonFinalFunctionArgsPos: NonFinalFunctionArgsPos = mutable.Map() + + val nonFinalFunctionArgs: mutable.Map[FunctionCall[V], NonFinalFunctionArgs] = mutable.Map() + + val entity2Function: mutable.Map[P, FunctionCall[V]] = mutable.Map() + /** * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, * however, only if `defSite` is not yet present. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index dd03b0c3cc..9400ba987c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -355,7 +355,8 @@ class InterproceduralStringAnalysis( eps match { case FinalP(p: StringConstancyProperty) ⇒ val resultEntity = eps.e.asInstanceOf[P] - // If necessary, update parameter information + // If necessary, update the parameter information with which the + // surrounding function / method of the entity was called with if (state.paramResultPositions.contains(resultEntity)) { val pos = state.paramResultPositions(resultEntity) state.params(pos._1)(pos._2) = p.stringConstancyInformation @@ -364,6 +365,28 @@ class InterproceduralStringAnalysis( state.dependees = state.dependees.filter(_.e != eps.e) } + // If necessary, update parameter information of function calls + if (state.entity2Function.contains(resultEntity)) { + state.appendToFpe2Sci( + state.var2IndexMapping(resultEntity._1), + p.stringConstancyInformation + ) + val func = state.entity2Function(resultEntity) + val pos = state.nonFinalFunctionArgsPos(func)(resultEntity) + val result = Result(resultEntity, p) + state.nonFinalFunctionArgs(func)(pos._1)(pos._2)(pos._3) = result + state.entity2Function.remove(resultEntity) + // Continue only after all necessary function parameters are evaluated + if (state.entity2Function.nonEmpty) { + return determinePossibleStrings(state) + } else { + state.entity2Function.clear() + if (!computeResultsForPath(state.computedLeanPath, state)) { + determinePossibleStrings(state) + } + } + } + if (state.isSetupCompleted && state.parameterDependeesCount == 0) { processFinalP(state, eps.e, p) } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index f3ceda2b60..3c639a2b7e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -1,18 +1,21 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation +import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.DefinedMethod import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -26,6 +29,8 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.FunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.NonFinalFunctionArgsPos +import org.opalj.tac.fpcf.analyses.string_analysis.P /** * @param cfg The control flow graph that underlies the instruction to interpret. @@ -114,18 +119,67 @@ abstract class AbstractStringInterpreter( /** * evaluateParameters takes a list of parameters, `params`, as produced, e.g., by * [[AbstractStringInterpreter.getParametersForPCs]], and an interpretation handler, `iHandler` - * and interprets the given parameters. + * and interprets the given parameters. The result list has the following format: The outer list + * corresponds to the lists of parameters passed to a function / method, the list in the middle + * corresponds to such lists and the inner-most list corresponds to the results / + * interpretations (this list is required as a concrete parameter may have more than one + * definition site). */ protected def evaluateParameters( - params: List[Seq[Expr[V]]], - iHandler: InterproceduralInterpretationHandler - ): ListBuffer[ListBuffer[StringConstancyInformation]] = params.map(_.map { expr ⇒ - val scis = expr.asVar.definedBy.map(iHandler.processDefSite(_, List())).map { r ⇒ - // TODO: Current assumption: Results of parameters are available right away - StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation - } - StringConstancyInformation.reduceMultiple(scis) - }.to[ListBuffer]).to[ListBuffer] + params: List[Seq[Expr[V]]], + iHandler: InterproceduralInterpretationHandler, + funCall: FunctionCall[V], + functionArgsPos: NonFinalFunctionArgsPos, + entity2Function: mutable.Map[P, FunctionCall[V]] + ): ListBuffer[ListBuffer[ListBuffer[ProperPropertyComputationResult]]] = params.zipWithIndex.map { + case (nextParamList, outerIndex) ⇒ + nextParamList.zipWithIndex.map { + case (nextParam, middleIndex) ⇒ + nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { + case (ds, innerIndex) ⇒ + val r = iHandler.processDefSite(ds, List()) + if (!r.isInstanceOf[Result]) { + val interim = r.asInstanceOf[InterimResult[StringConstancyProperty]] + if (!functionArgsPos.contains(funCall)) { + functionArgsPos(funCall) = mutable.Map() + } + val e = interim.eps.e.asInstanceOf[P] + functionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) + entity2Function(e) = funCall + } + r + }.to[ListBuffer] + }.to[ListBuffer] + }.to[ListBuffer] + + /** + * This function checks whether the interpretation of parameters, as, e.g., produced by + * [[evaluateParameters()]], is final or not. If the given parameters contain at least one + * element not of type [[Result]], this function returns such a non-final result. Otherwise, if + * all computation results are final, this function returns `None`. + */ + protected def getNonFinalParameters( + evaluatedParameters: Seq[Seq[Seq[ProperPropertyComputationResult]]] + ): List[InterimResult[StringConstancyProperty]] = + evaluatedParameters.flatten.flatten.filter { !_.isInstanceOf[Result] }.map { + _.asInstanceOf[InterimResult[StringConstancyProperty]] + }.toList + + /** + * convertEvaluatedParameters takes a list of evaluated / interpreted parameters as, e.g., + * produced by [[evaluateParameters]] and transforms these into a list of lists where the inner + * lists are the reduced [[StringConstancyInformation]]. Note that this function assumes that + * all results in the inner-most sequence are final! + */ + protected def convertEvaluatedParameters( + evaluatedParameters: Seq[Seq[Seq[ProperPropertyComputationResult]]] + ): ListBuffer[ListBuffer[StringConstancyInformation]] = evaluatedParameters.map { paramList ⇒ + paramList.map { param ⇒ + StringConstancyInformation.reduceMultiple(param.map { paramInterpr ⇒ + StringConstancyProperty.extractFromPPCR(paramInterpr).stringConstancyInformation + }) + }.to[ListBuffer] + }.to[ListBuffer] /** * diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 7b9d6817f6..857729ad8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -27,7 +27,7 @@ abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { /** * A list of definition sites that have already been processed. */ - protected val processedDefSites: ListBuffer[Int] = ListBuffer[Int]() + protected val processedDefSites: ListBuffer[Int] = ListBuffer[Int]() // TODO: Maybe a map is advantageous /** * Processes a given definition site. That is, this function determines the interpretation of diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index b12e66d2f3..bcb425b8a6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -122,13 +122,27 @@ class InterproceduralInterpretationHandler( case ExprStmt(_, expr: GetStatic) ⇒ new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - new VirtualFunctionCallPreparationInterpreter( + val r = new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) + // In case no final result could be computed, remove this def site from the list of + // processed def sites to make sure that is can be compute again (when all final + // results are available) + if (state.nonFinalFunctionArgs.contains(expr)) { + processedDefSites.remove(processedDefSites.indexOf(defSite)) + } + r case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ - new InterproceduralStaticFunctionCallInterpreter( + val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) + // In case no final result could be computed, remove this def site from the list of + // processed def sites to make sure that is can be compute again (when all final + // results are available) + if (state.nonFinalFunctionArgs.contains(expr)) { + processedDefSites.remove(processedDefSites.indexOf(defSite)) + } + r case Assignment(_, _, expr: BinaryExpr[V]) ⇒ val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) @@ -140,13 +154,27 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: GetField[V]) ⇒ new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ - new VirtualFunctionCallPreparationInterpreter( + val r = new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) + // In case no final result could be computed, remove this def site from the list of + // processed def sites to make sure that is can be compute again (when all final + // results are available) + if (state.nonFinalFunctionArgs.contains(expr)) { + processedDefSites.remove(processedDefSites.indexOf(defSite)) + } + r case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ - new InterproceduralStaticFunctionCallInterpreter( + val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) + // In case no final result could be computed, remove this def site from the list of + // processed def sites to make sure that is can be compute again (when all final + // results are available) + if (!r.isInstanceOf[Result]) { + processedDefSites.remove(processedDefSites.length - 1) + } + r case vmc: VirtualMethodCall[V] ⇒ new InterproceduralVirtualMethodCallInterpreter( cfg, this, callees diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 96b7f07a88..23896caf4f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -71,15 +71,37 @@ class InterproceduralStaticFunctionCallInterpreter( calledMethods.exists(m ⇒ m.name == instr.name && m.declaringClassType == instr.declaringClass) }.keys + // Collect all parameters - val params = evaluateParameters(getParametersForPCs(relevantPCs, state.tac), exprHandler) + val isFunctionArgsPreparationDone = state.nonFinalFunctionArgs.contains(instr) + val params = if (isFunctionArgsPreparationDone) { + state.nonFinalFunctionArgs(instr) + } else { + evaluateParameters( + getParametersForPCs(relevantPCs, state.tac), + exprHandler, + instr, + state.nonFinalFunctionArgsPos, + state.entity2Function + ) + } + if (!isFunctionArgsPreparationDone) { + val nonFinalResults = getNonFinalParameters(params) + if (nonFinalResults.nonEmpty) { + state.nonFinalFunctionArgs(instr) = params + return nonFinalResults.head + } + } + state.nonFinalFunctionArgs.remove(instr) + state.nonFinalFunctionArgsPos.remove(instr) + val evaluatedParams = convertEvaluatedParameters(params) if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, m) - InterproceduralStringAnalysis.registerParams(entity, params) + InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index ea5f09be74..3cfeeeace2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -121,8 +121,30 @@ class VirtualFunctionCallPreparationInterpreter( m.name == instr.name && mClassName == instrClassName } }.keys + // Collect all parameters - val params = evaluateParameters(getParametersForPCs(relevantPCs, state.tac), exprHandler) + val isFunctionArgsPreparationDone = state.nonFinalFunctionArgs.contains(instr) + val params = if (isFunctionArgsPreparationDone) { + state.nonFinalFunctionArgs(instr) + } else { + evaluateParameters( + getParametersForPCs(relevantPCs, state.tac), + exprHandler, + instr, + state.nonFinalFunctionArgsPos, + state.entity2Function + ) + } + if (!isFunctionArgsPreparationDone) { + val nonFinalResults = getNonFinalParameters(params) + if (nonFinalResults.nonEmpty) { + state.nonFinalFunctionArgs(instr) = params + return nonFinalResults.head + } + } + state.nonFinalFunctionArgs.remove(instr) + state.nonFinalFunctionArgsPos.remove(instr) + val evaluatedParams = convertEvaluatedParameters(params) val results = methods.map { nextMethod ⇒ val tac = getTACAI(ps, nextMethod, state) @@ -132,7 +154,7 @@ class VirtualFunctionCallPreparationInterpreter( val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, nextMethod) - InterproceduralStringAnalysis.registerParams(entity, params) + InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { case r: Result ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index f32bbe5571..1ce9ddec0a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -1,9 +1,14 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.value.ValueInformation import org.opalj.br.Method import org.opalj.tac.DUVar +import org.opalj.tac.FunctionCall /** * @author Patrick Mell @@ -23,4 +28,7 @@ package object string_analysis { */ type P = (V, Method) + type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[V], mutable.Map[P, (Int, Int, Int)]] + type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[ProperPropertyComputationResult]]] + } From 56c174bf6d381110dc342820bd3395dbc36a8db7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 08:45:25 +0100 Subject: [PATCH 199/316] Fixed a little bug. --- .../interprocedural/ArrayPreparationInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index a0d8fd5db0..115a8a5d2d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -64,7 +64,7 @@ class ArrayPreparationInterpreter( // Add information of parameters defSites.filter(_ < 0).foreach { ds ⇒ - val paramPos = Math.abs(defSite + 2) + val paramPos = Math.abs(ds + 2) // lb is the fallback value val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) val e: Integer = ds From e894819d9580d60dc691cb6311e860a4bec8cff1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 09:42:13 +0100 Subject: [PATCH 200/316] Having the TAC and the CFG stored together is redundant => CFG was removed. --- .../InterproceduralComputationState.scala | 7 ------ .../InterproceduralStringAnalysis.scala | 9 +++----- .../IntraproceduralStringAnalysis.scala | 23 +++++++++++-------- .../InterpretationHandler.scala | 14 +++++++---- ...InterproceduralInterpretationHandler.scala | 18 +++++++-------- ...IntraproceduralInterpretationHandler.scala | 17 +++++++------- 6 files changed, 41 insertions(+), 47 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 61d3198f11..ab8045375c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -9,17 +9,14 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.fpcf.Result import org.opalj.value.ValueInformation -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.DUVar import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode -import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.FunctionCall @@ -35,10 +32,6 @@ case class InterproceduralComputationState(entity: P) { * The Three-Address Code of the entity's method */ var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ - /** - * The Control Flow Graph of the entity's method - */ - var cfg: CFG[Stmt[V], TACStmts[V]] = _ /** * The interpretation handler to use */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 9400ba987c..c3a775da85 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -134,12 +134,9 @@ class InterproceduralStringAnalysis( val defSites = uvar.definedBy.toArray.sorted val tacProvider = p.get(SimpleTACAIKey) - if (state.cfg == null) { - state.cfg = tacProvider(state.entity._2).cfg - } if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( - state.cfg, ps, declaredMethods, state, continuation(state) + state.tac, ps, declaredMethods, state, continuation(state) ) } @@ -197,7 +194,7 @@ class InterproceduralStringAnalysis( // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val stmts = state.cfg.code.instructions + val stmts = state.tac.stmts // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { @@ -206,7 +203,7 @@ class InterproceduralStringAnalysis( return Result(state.entity, StringConstancyProperty.extractFromPPCR(r)) } - val pathFinder: AbstractPathFinder = new WindowPathFinder(state.cfg) + val pathFinder: AbstractPathFinder = new WindowPathFinder(state.tac.cfg) val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index b0a9c91af6..2daac06148 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -15,8 +15,8 @@ import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS +import org.opalj.value.ValueInformation import org.opalj.br.analyses.SomeProject -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler @@ -26,7 +26,6 @@ import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ExprStmt import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder @@ -37,6 +36,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode /** * IntraproceduralStringAnalysis processes a read operation of a local string variable at a program @@ -78,16 +80,17 @@ class IntraproceduralStringAnalysis( var2IndexMapping: mutable.Map[V, Int], // A mapping from values of FlatPathElements to StringConstancyInformation fpe2sci: mutable.Map[Int, StringConstancyInformation], - // The control flow graph on which the computedLeanPath is based - cfg: CFG[Stmt[V], TACStmts[V]] + // The three-address code of the method in which the entity under analysis resides + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ) def analyze(data: P): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation val tacProvider = p.get(SimpleTACAIKey) - val cfg = tacProvider(data._2).cfg - val stmts = cfg.code.instructions + val tac = tacProvider(data._2) + val cfg = tac.cfg + val stmts = tac.stmts val uvar = data._1 val defSites = uvar.definedBy.toArray.sorted @@ -122,7 +125,7 @@ class IntraproceduralStringAnalysis( dependentVars.keys.foreach { nextVar ⇒ val toAnalyze = (nextVar, data._2) val fpe2sci = mutable.Map[Int, StringConstancyInformation]() - state = ComputationState(leanPaths, dependentVars, fpe2sci, cfg) + state = ComputationState(leanPaths, dependentVars, fpe2sci, tac) val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ @@ -135,14 +138,14 @@ class IntraproceduralStringAnalysis( } } } else { - val interpretationHandler = IntraproceduralInterpretationHandler(cfg) + val interpretationHandler = IntraproceduralInterpretationHandler(tac) sci = new PathTransformer( interpretationHandler ).pathToStringTree(leanPaths).reduce(true) } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - val interHandler = IntraproceduralInterpretationHandler(cfg) + val interHandler = IntraproceduralInterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( uvar.definedBy.toArray.sorted.map { ds ⇒ val r = interHandler.processDefSite(ds).asInstanceOf[Result] @@ -183,7 +186,7 @@ class IntraproceduralStringAnalysis( // No more dependees => Return the result for this analysis run val remDependees = dependees.filter(_.e != e) if (remDependees.isEmpty) { - val interpretationHandler = IntraproceduralInterpretationHandler(state.cfg) + val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci.map { case (k, v) ⇒ (k, ListBuffer(v)) }.toMap ).reduce(true) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 857729ad8c..e4890c1758 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -5,6 +5,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.value.ValueInformation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -16,14 +17,17 @@ import org.opalj.tac.New import org.opalj.tac.Stmt import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode import org.opalj.tac.TACStmts -abstract class InterpretationHandler(cfg: CFG[Stmt[V], TACStmts[V]]) { +abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { + + protected val stmts: Array[Stmt[DUVar[ValueInformation]]] = tac.stmts + protected val cfg: CFG[Stmt[DUVar[ValueInformation]], TACStmts[DUVar[ValueInformation]]] = + tac.cfg - /** - * The statements of the given [[cfg]]. - */ - protected val stmts: Array[Stmt[V]] = cfg.code.instructions /** * A list of definition sites that have already been processed. */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index bcb425b8a6..5c4805f2a7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -5,12 +5,10 @@ import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result +import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods -import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment @@ -38,7 +36,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringC import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NonVirtualMethodCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.VirtualFunctionCallFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.DUVar import org.opalj.tac.GetStatic +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -49,18 +50,15 @@ import org.opalj.tac.GetStatic * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter]]) can * either return a final or intermediate result. * - * @param cfg The control flow graph that underlies the program / method in which the expressions of - * interest reside. - * * @author Patrick Mell */ class InterproceduralInterpretationHandler( - cfg: CFG[Stmt[V], TACStmts[V]], + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], ps: PropertyStore, declaredMethods: DeclaredMethods, state: InterproceduralComputationState, c: ProperOnUpdateContinuation -) extends InterpretationHandler(cfg) { +) extends InterpretationHandler(tac) { /** * Processed the given definition site in an interprocedural fashion. @@ -216,13 +214,13 @@ object InterproceduralInterpretationHandler { * @see [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] */ def apply( - cfg: CFG[Stmt[V], TACStmts[V]], + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], ps: PropertyStore, declaredMethods: DeclaredMethods, state: InterproceduralComputationState, c: ProperOnUpdateContinuation ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - cfg, ps, declaredMethods, state, c + tac, ps, declaredMethods, state, c ) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala index 86b442d9a1..8fc3e2e670 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala @@ -3,7 +3,7 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedur import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Result -import org.opalj.br.cfg.CFG +import org.opalj.value.ValueInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ArrayLoad @@ -16,9 +16,7 @@ import org.opalj.tac.New import org.opalj.tac.NonVirtualFunctionCall import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.StaticFunctionCall -import org.opalj.tac.Stmt import org.opalj.tac.StringConst -import org.opalj.tac.TACStmts import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.VirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -31,6 +29,9 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.Integer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode /** * `IntraproceduralInterpretationHandler` is responsible for processing expressions that are @@ -41,13 +42,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringC * [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter]]) return * a final computation result! * - * @param cfg The control flow graph that underlies the program / method in which the expressions of - * interest reside. * @author Patrick Mell */ class IntraproceduralInterpretationHandler( - cfg: CFG[Stmt[V], TACStmts[V]] -) extends InterpretationHandler(cfg) { + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] +) extends InterpretationHandler(tac) { /** * Processed the given definition site in an intraprocedural fashion. @@ -121,7 +120,7 @@ object IntraproceduralInterpretationHandler { * @see [[IntraproceduralInterpretationHandler]] */ def apply( - cfg: CFG[Stmt[V], TACStmts[V]] - ): IntraproceduralInterpretationHandler = new IntraproceduralInterpretationHandler(cfg) + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + ): IntraproceduralInterpretationHandler = new IntraproceduralInterpretationHandler(tac) } From 95351062fa0b1b82b7fdbc5a90073ff65599ebd1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 11:29:06 +0100 Subject: [PATCH 201/316] Added further comments / explanations regarding the feature implemented by commit #8da0654. --- .../InterproceduralComputationState.scala | 34 +++++++++++++++++-- .../InterproceduralStringAnalysis.scala | 6 ++++ .../AbstractStringInterpreter.scala | 15 ++++---- ...ceduralStaticFunctionCallInterpreter.scala | 5 ++- ...alFunctionCallPreparationInterpreter.scala | 5 ++- .../string_analysis/string_analysis.scala | 17 +++++++++- 6 files changed, 70 insertions(+), 12 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index ab8045375c..2699957803 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -32,44 +32,54 @@ case class InterproceduralComputationState(entity: P) { * The Three-Address Code of the entity's method */ var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ + /** * The interpretation handler to use */ var iHandler: InterproceduralInterpretationHandler = _ + /** * The computed lean path that corresponds to the given entity */ var computedLeanPath: Path = _ + /** * Callees information regarding the declared method that corresponds to the entity's method */ var callees: Callees = _ + /** * Callers information regarding the declared method that corresponds to the entity's method */ var callers: CallersProperty = _ + /** * If not empty, this routine can only produce an intermediate result */ var dependees: List[EOptionP[Entity, Property]] = List() + /** * A mapping from DUVar elements to the corresponding indices of the FlatPathElements */ val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() + /** * A mapping from values / indices of FlatPathElements to StringConstancyInformation */ val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() + /** * An analysis may depend on the evaluation of its parameters. This number indicates how many * of such dependencies are still to be computed. */ var parameterDependeesCount = 0 + /** * Indicates whether the basic setup of the string analysis is done. This value is to be set to * `true`, when all necessary dependees and parameters are available. */ var isSetupCompleted = false + /** * It might be that the result of parameters, which have to be evaluated, is not available right * away. Later on, when the result is available, it is necessary to map it to the right @@ -78,15 +88,33 @@ case class InterproceduralComputationState(entity: P) { * index of the method and the second value the position of the parameter. */ val paramResultPositions: mutable.Map[P, (Int, Int)] = mutable.Map() - // Parameter values of a method / function. The structure of this field is as follows: Each item - // in the outer list holds the parameters of a concrete call. A mapping from the definition - // sites of parameter (negative values) to a correct index of `params` has to be made! + + /** + * Parameter values of a method / function. The structure of this field is as follows: Each item + * in the outer list holds the parameters of a concrete call. A mapping from the definition + * sites of parameter (negative values) to a correct index of `params` has to be made! + */ var params: ListBuffer[ListBuffer[StringConstancyInformation]] = ListBuffer() + /** + * This map is used to store information regarding arguments of function calls. In case a + * function is passed as a function parameter, the result might not be available right away but + * needs to be mapped to the correct param element of [[nonFinalFunctionArgs]] when available. + * For this, this map is used. + * For further information, see [[NonFinalFunctionArgsPos]]. + */ val nonFinalFunctionArgsPos: NonFinalFunctionArgsPos = mutable.Map() + /** + * This map is used to actually store the interpretations of parameters passed to functions. + * For further information, see [[NonFinalFunctionArgs]]. + */ val nonFinalFunctionArgs: mutable.Map[FunctionCall[V], NonFinalFunctionArgs] = mutable.Map() + /** + * During the process of updating the [[nonFinalFunctionArgs]] map, it is necessary to find out + * to which function an entity belongs. We use the following map to do this in constant time. + */ val entity2Function: mutable.Map[P, FunctionCall[V]] = mutable.Map() /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index c3a775da85..8465c16625 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -368,6 +368,7 @@ class InterproceduralStringAnalysis( state.var2IndexMapping(resultEntity._1), p.stringConstancyInformation ) + // Update the state val func = state.entity2Function(resultEntity) val pos = state.nonFinalFunctionArgsPos(func)(resultEntity) val result = Result(resultEntity, p) @@ -377,6 +378,11 @@ class InterproceduralStringAnalysis( if (state.entity2Function.nonEmpty) { return determinePossibleStrings(state) } else { + // We could try to determine a final result before all function + // parameter information are available, however, this will + // definitely result in finding some intermediate result. Thus, + // defer this computations when we know that all necessary + // information are available state.entity2Function.clear() if (!computeResultsForPath(state.computedLeanPath, state)) { determinePossibleStrings(state) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 3c639a2b7e..407508bdc3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -29,6 +29,7 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.FunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.NonFinalFunctionArgs import org.opalj.tac.fpcf.analyses.string_analysis.NonFinalFunctionArgsPos import org.opalj.tac.fpcf.analyses.string_analysis.P @@ -124,14 +125,17 @@ abstract class AbstractStringInterpreter( * corresponds to such lists and the inner-most list corresponds to the results / * interpretations (this list is required as a concrete parameter may have more than one * definition site). + * For housekeeping, this function takes the function call, `funCall`, of which parameters are + * to be evaluated as well as function argument positions, `functionArgsPos`, and a mapping from + * entities to functions, `entity2function`. */ protected def evaluateParameters( params: List[Seq[Expr[V]]], iHandler: InterproceduralInterpretationHandler, funCall: FunctionCall[V], functionArgsPos: NonFinalFunctionArgsPos, - entity2Function: mutable.Map[P, FunctionCall[V]] - ): ListBuffer[ListBuffer[ListBuffer[ProperPropertyComputationResult]]] = params.zipWithIndex.map { + entity2function: mutable.Map[P, FunctionCall[V]] + ): NonFinalFunctionArgs = params.zipWithIndex.map { case (nextParamList, outerIndex) ⇒ nextParamList.zipWithIndex.map { case (nextParam, middleIndex) ⇒ @@ -145,7 +149,7 @@ abstract class AbstractStringInterpreter( } val e = interim.eps.e.asInstanceOf[P] functionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) - entity2Function(e) = funCall + entity2function(e) = funCall } r }.to[ListBuffer] @@ -154,9 +158,8 @@ abstract class AbstractStringInterpreter( /** * This function checks whether the interpretation of parameters, as, e.g., produced by - * [[evaluateParameters()]], is final or not. If the given parameters contain at least one - * element not of type [[Result]], this function returns such a non-final result. Otherwise, if - * all computation results are final, this function returns `None`. + * [[evaluateParameters()]], is final or not and returns all results not of type [[Result]] as a + * list. Hence, if this function returns an empty list, all parameters are fully evaluated. */ protected def getNonFinalParameters( evaluatedParameters: Seq[Seq[Seq[ProperPropertyComputationResult]]] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 23896caf4f..eda5a2fb8a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -72,7 +72,9 @@ class InterproceduralStaticFunctionCallInterpreter( m.name == instr.name && m.declaringClassType == instr.declaringClass) }.keys - // Collect all parameters + // Collect all parameters; either from the state, if the interpretation of instr was started + // before (in this case, the assumption is that all parameters are fully interpreted) or + // start a new interpretation val isFunctionArgsPreparationDone = state.nonFinalFunctionArgs.contains(instr) val params = if (isFunctionArgsPreparationDone) { state.nonFinalFunctionArgs(instr) @@ -85,6 +87,7 @@ class InterproceduralStaticFunctionCallInterpreter( state.entity2Function ) } + // Continue only when all parameter information are available if (!isFunctionArgsPreparationDone) { val nonFinalResults = getNonFinalParameters(params) if (nonFinalResults.nonEmpty) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 3cfeeeace2..07c8d1f7b4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -122,7 +122,9 @@ class VirtualFunctionCallPreparationInterpreter( } }.keys - // Collect all parameters + // Collect all parameters; either from the state, if the interpretation of instr was started + // before (in this case, the assumption is that all parameters are fully interpreted) or + // start a new interpretation val isFunctionArgsPreparationDone = state.nonFinalFunctionArgs.contains(instr) val params = if (isFunctionArgsPreparationDone) { state.nonFinalFunctionArgs(instr) @@ -135,6 +137,7 @@ class VirtualFunctionCallPreparationInterpreter( state.entity2Function ) } + // Continue only when all parameter information are available if (!isFunctionArgsPreparationDone) { val nonFinalResults = getNonFinalParameters(params) if (nonFinalResults.nonEmpty) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index 1ce9ddec0a..96633affe7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -28,7 +28,22 @@ package object string_analysis { */ type P = (V, Method) - type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[V], mutable.Map[P, (Int, Int, Int)]] + /** + * This type indicates how (non-final) parameters of functions are represented. The outer-most + * list holds all parameter lists a function is called with. The list in the middle holds the + * parameters of a concrete call and the inner-most list holds interpreted parameters. The + * reason for the inner-most list is that a parameter might have different definition sites; to + * capture all, the third (inner-most) list is necessary. + */ type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[ProperPropertyComputationResult]]] + /** + * This type serves as a lookup mechanism to find out which functions parameters map to which + * argument position. That is, the element of type [[P]] of the inner map maps from this entity + * to its position in a data structure of type [[NonFinalFunctionArgs]]. The outer map is + * necessary to uniquely identify a position as an entity might be used for different function + * calls. + */ + type NonFinalFunctionArgsPos = mutable.Map[FunctionCall[V], mutable.Map[P, (Int, Int, Int)]] + } From 8160f2bb47d9b3c6628b0bba5f111fbc4d18e388 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 11:38:36 +0100 Subject: [PATCH 202/316] Turned InterpretationHandler#processedDefSites into a map to have constant look-up times. --- .../interpretation/InterpretationHandler.scala | 5 +++-- .../InterproceduralInterpretationHandler.scala | 18 +++++++++--------- .../IntraproceduralInterpretationHandler.scala | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index e4890c1758..9c66e8572b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -29,9 +29,10 @@ abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[Value tac.cfg /** - * A list of definition sites that have already been processed. + * A list of definition sites that have already been processed. Store it as a map for constant + * loop-ups (the value is not relevant and thus set to [[Unit]]). */ - protected val processedDefSites: ListBuffer[Int] = ListBuffer[Int]() // TODO: Maybe a map is advantageous + protected val processedDefSites: mutable.Map[Int, Unit] = mutable.Map() /** * Processes a given definition site. That is, this function determines the interpretation of diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 5c4805f2a7..5acc8831a8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -85,29 +85,29 @@ class InterproceduralInterpretationHandler( return Result(e, StringConstancyProperty.getNeutralElement) } // Note that def sites referring to constant expressions will be deleted further down - processedDefSites.append(defSite) + processedDefSites(defSite) = Unit val callees = state.callees stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ val result = new StringConstInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) - processedDefSites.remove(processedDefSites.length - 1) + processedDefSites.remove(defSite) result case Assignment(_, _, expr: IntConst) ⇒ val result = new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) - processedDefSites.remove(processedDefSites.length - 1) + processedDefSites.remove(defSite) result case Assignment(_, _, expr: FloatConst) ⇒ val result = new FloatValueInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) - processedDefSites.remove(processedDefSites.length - 1) + processedDefSites.remove(defSite) result case Assignment(_, _, expr: DoubleConst) ⇒ val result = new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) - processedDefSites.remove(processedDefSites.length - 1) + processedDefSites.remove(defSite) result case Assignment(_, _, expr: ArrayLoad[V]) ⇒ new ArrayPreparationInterpreter(cfg, this, state, params).interpret(expr, defSite) @@ -127,7 +127,7 @@ class InterproceduralInterpretationHandler( // processed def sites to make sure that is can be compute again (when all final // results are available) if (state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(processedDefSites.indexOf(defSite)) + processedDefSites.remove(defSite) } r case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ @@ -138,7 +138,7 @@ class InterproceduralInterpretationHandler( // processed def sites to make sure that is can be compute again (when all final // results are available) if (state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(processedDefSites.indexOf(defSite)) + processedDefSites.remove(defSite) } r case Assignment(_, _, expr: BinaryExpr[V]) ⇒ @@ -159,7 +159,7 @@ class InterproceduralInterpretationHandler( // processed def sites to make sure that is can be compute again (when all final // results are available) if (state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(processedDefSites.indexOf(defSite)) + processedDefSites.remove(defSite) } r case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ @@ -170,7 +170,7 @@ class InterproceduralInterpretationHandler( // processed def sites to make sure that is can be compute again (when all final // results are available) if (!r.isInstanceOf[Result]) { - processedDefSites.remove(processedDefSites.length - 1) + processedDefSites.remove(defSite) } r case vmc: VirtualMethodCall[V] ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala index 8fc3e2e670..21fa873404 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala @@ -65,7 +65,7 @@ class IntraproceduralInterpretationHandler( } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } - processedDefSites.append(defSite) + processedDefSites(defSite) = Unit val result: ProperPropertyComputationResult = stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ From 07b28fa9b5b6bfd6c93a28cd8e12fcc8be4f1b30 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 19:48:03 +0100 Subject: [PATCH 203/316] Improved the analysis in the way that it does not collect callers information in case a string constant, which is not a parameter, is analyzed. --- .../InterproceduralTestMethods.java | 15 ++++++++++++++- .../InterproceduralStringAnalysis.scala | 17 +++++++++++------ .../interpretation/InterpretationHandler.scala | 9 +++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 5c978f1f24..de613aa632 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -219,7 +219,7 @@ public void knownHierarchyInstanceTest() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(Hello World|Hello)" + expectedStrings = "(Hello|Hello World)" ) }) @@ -345,6 +345,19 @@ public void functionWithFunctionParameter() { analyzeString(addQuestionMark(getHelloWorld())); } + @StringDefinitionsCollection( + value = "a case where no callers information need to be computed", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "java.lang.String" + ) + }) + public void noCallersInformationRequiredTest(String s) { + System.out.println(s); + analyzeString("java.lang.String"); + } + /** * Necessary for the callerWithFunctionParameterTest. */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 8465c16625..c9b417b46b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -132,6 +132,7 @@ class InterproceduralStringAnalysis( ): ProperPropertyComputationResult = { val uvar = state.entity._1 val defSites = uvar.definedBy.toArray.sorted + val stmts = state.tac.stmts val tacProvider = p.get(SimpleTACAIKey) if (state.iHandler == null) { @@ -140,13 +141,19 @@ class InterproceduralStringAnalysis( ) } - // In case a parameter is required for approximating a string, retrieve callers information - // (but only once) if (state.params.isEmpty) { state.params = InterproceduralStringAnalysis.getParams(state.entity) } - if (state.entity._2.parameterTypes.length > 0 && - state.callers == null && state.params.isEmpty) { + // In case a parameter is required for approximating a string, retrieve callers information + // (but only once and only if the expressions is not a local string) + val requiresCallersInfo = if (state.entity._1.definedBy.exists(_ < 0)) { + state.entity._2.parameterTypes.length > 0 && state.callers == null && + state.params.isEmpty + } else { + !InterpretationHandler.isStringConstExpression(stmts(defSites.head).asAssignment.expr) + } + + if (requiresCallersInfo) { val declaredMethods = project.get(DeclaredMethodsKey).declaredMethods val dm = declaredMethods.find { dm ⇒ // TODO: What is a better / more robust way to compare methods (a check with == did @@ -194,8 +201,6 @@ class InterproceduralStringAnalysis( // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val stmts = state.tac.stmts - // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { state.computedLeanPath = Path(List(FlatPathElement(defSites.head))) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 9c66e8572b..e2b1cf2d32 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -93,6 +93,15 @@ object InterpretationHandler { case _ ⇒ false } + /** + * Checks whether the given expression is a string constant / string literal. + * + * @param expr The expression to check. + * @return Returns `true` if the given expression is a string constant / literal and `false` + * otherwise. + */ + def isStringConstExpression(expr: Expr[V]): Boolean = expr.isStringConst + /** * Checks whether an expression contains a call to [[StringBuilder#append]] or * [[StringBuffer#append]]. From 1c9de0771e213093c5e1b5d678931c0057736e0e Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 28 Feb 2019 19:49:04 +0100 Subject: [PATCH 204/316] Exception information are regarded as dynamic, i.e., lower bound. --- .../InterproceduralInterpretationHandler.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 5acc8831a8..d0b839a10e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -9,6 +9,7 @@ import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ArrayLoad import org.opalj.tac.Assignment @@ -73,8 +74,9 @@ class InterproceduralInterpretationHandler( // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt // Function parameters are not evaluated when none are present (this always includes the - // implicit parameter for "this") - if (defSite < 0 && (params.isEmpty || defSite == -1)) { + // implicit parameter for "this" and for exceptions thrown outside the current function) + if (defSite < 0 && + (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset)) { return Result(e, StringConstancyProperty.lb) } else if (defSite < 0) { val paramPos = Math.abs(defSite + 2) From 3685c83712b0351992cb8c46992079e45aa6906a Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Mar 2019 12:48:01 +0100 Subject: [PATCH 205/316] Callers information are only collected if at least one of the parameters is of a supported type. --- .../InterproceduralStringAnalysis.scala | 119 ++++++++++-------- 1 file changed, 70 insertions(+), 49 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index c9b417b46b..cd9b84a297 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -25,6 +25,7 @@ import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.FieldType import org.opalj.br.Method import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder @@ -147,8 +148,10 @@ class InterproceduralStringAnalysis( // In case a parameter is required for approximating a string, retrieve callers information // (but only once and only if the expressions is not a local string) val requiresCallersInfo = if (state.entity._1.definedBy.exists(_ < 0)) { - state.entity._2.parameterTypes.length > 0 && state.callers == null && - state.params.isEmpty + state.entity._2.parameterTypes.length > 0 && + state.entity._2.parameterTypes.exists { + InterproceduralStringAnalysis.isSupportedType + } && state.callers == null && state.params.isEmpty } else { !InterpretationHandler.isStringConstExpression(stmts(defSites.head).asAssignment.expr) } @@ -161,7 +164,7 @@ class InterproceduralStringAnalysis( dm.name == state.entity._2.name && dm.declaringClassType.toJava == state.entity._2.classFile.thisType.toJava && dm.definedMethod.descriptor.parameterTypes.length == - state.entity._2.descriptor.parameterTypes.length + state.entity._2.descriptor.parameterTypes.length }.get val callersEOptP = ps(dm, CallersProperty.key) if (callersEOptP.hasUBP) { @@ -437,9 +440,7 @@ class InterproceduralStringAnalysis( * not have been called)! * @return Returns the final result. */ - private def computeFinalResult( - state: InterproceduralComputationState, - ): Result = { + private def computeFinalResult(state: InterproceduralComputationState): Result = { finalizePreparations(state.computedLeanPath, state, state.iHandler) val finalSci = new PathTransformer(null).pathToStringTree( state.computedLeanPath, state.fpe2sci.toMap, resetExprHandler = false @@ -483,7 +484,7 @@ class InterproceduralStringAnalysis( * interpretations are registered using [[InterproceduralStringAnalysis.registerParams]]. */ private def registerParams( - state: InterproceduralComputationState, + state: InterproceduralComputationState, tacProvider: Method ⇒ TACode[TACMethodParameter, DUVar[ValueInformation]] ): Boolean = { var hasIntermediateResult = false @@ -492,38 +493,38 @@ class InterproceduralStringAnalysis( val tac = propertyStore(m.definedMethod, TACAI.key).ub.tac.get val params = tac.stmts(tac.pcToIndex(pc)) match { case Assignment(_, _, fc: FunctionCall[V]) ⇒ fc.params - case Assignment(_, _, mc: MethodCall[V]) ⇒ mc.params - case ExprStmt(_, fc: FunctionCall[V]) ⇒ fc.params - case ExprStmt(_, fc: MethodCall[V]) ⇒ fc.params - case mc: MethodCall[V] ⇒ mc.params - case _ ⇒ List() + case Assignment(_, _, mc: MethodCall[V]) ⇒ mc.params + case ExprStmt(_, fc: FunctionCall[V]) ⇒ fc.params + case ExprStmt(_, fc: MethodCall[V]) ⇒ fc.params + case mc: MethodCall[V] ⇒ mc.params + case _ ⇒ List() } - params.zipWithIndex.foreach { case (p, paramIndex) ⇒ - // Add an element to the params list (we do it here because we know how many - // parameters there are) - if (state.params.length <= methodIndex) { - state.params.append(params.indices.map(_ ⇒ - StringConstancyInformation.getNeutralElement - ).to[ListBuffer]) - } - // Recursively analyze supported types - if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { - val paramEntity = (p.asVar, m.definedMethod) - val eps = propertyStore(paramEntity, StringConstancyProperty.key) - state.var2IndexMapping(paramEntity._1) = paramIndex - eps match { - case FinalP(r) ⇒ - state.params(methodIndex)(paramIndex) = r.stringConstancyInformation - case _ ⇒ - state.dependees = eps :: state.dependees - hasIntermediateResult = true - state.paramResultPositions(paramEntity) = (methodIndex, paramIndex) - state.parameterDependeesCount += 1 + params.zipWithIndex.foreach { + case (p, paramIndex) ⇒ + // Add an element to the params list (we do it here because we know how many + // parameters there are) + if (state.params.length <= methodIndex) { + state.params.append(params.indices.map(_ ⇒ + StringConstancyInformation.getNeutralElement).to[ListBuffer]) + } + // Recursively analyze supported types + if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { + val paramEntity = (p.asVar, m.definedMethod) + val eps = propertyStore(paramEntity, StringConstancyProperty.key) + state.var2IndexMapping(paramEntity._1) = paramIndex + eps match { + case FinalP(r) ⇒ + state.params(methodIndex)(paramIndex) = r.stringConstancyInformation + case _ ⇒ + state.dependees = eps :: state.dependees + hasIntermediateResult = true + state.paramResultPositions(paramEntity) = (methodIndex, paramIndex) + state.parameterDependeesCount += 1 + } + } else { + state.params(methodIndex)(paramIndex) = + StringConstancyProperty.lb.stringConstancyInformation } - } else { - state.params(methodIndex)(paramIndex) = - StringConstancyProperty.lb.stringConstancyInformation - } } } @@ -538,14 +539,14 @@ class InterproceduralStringAnalysis( * This function traverses the given path, computes all string values along the path and stores * these information in the given state. * - * @param p The path to traverse. + * @param p The path to traverse. * @param state The current state of the computation. This function will alter * [[InterproceduralComputationState.fpe2sci]]. * @return Returns `true` if all values computed for the path are final results. */ private def computeResultsForPath( - p: Path, - state: InterproceduralComputationState + p: Path, + state: InterproceduralComputationState ): Boolean = { var hasFinalResult = true @@ -684,26 +685,46 @@ object InterproceduralStringAnalysis { } /** - * Checks whether a value of some type is supported by the [[InterproceduralStringAnalysis]]. - * Currently supported types are, java.lang.String and the primitive types short, int, float, - * and double. + * Checks whether a given type, identified by its string representation, is supported by the + * string analysis. That means, if this function returns `true`, a value, which is of type + * `typeName` may be approximated by the string analysis better than just the lower bound. * - * @param v The value to check if it is supported. - * @return Returns `true` if `v` is a supported type and `false` otherwise. + * @param typeName The name of the type to check. May either be the name of a primitive type or + * a fully-qualified class name (dot-separated). + * @return Returns `true`, if `typeName` is an element in [char, short, int, float, double, + * java.lang.String] and `false` otherwise. + */ + def isSupportedType(typeName: String): Boolean = + typeName == "char" || typeName == "short" || typeName == "int" || typeName == "float" || + typeName == "double" || typeName == "java.lang.String" + + /** + * Determines whether a given [[V]] element ([[DUVar]]) is supported by the string analysis. + * + * @param v The element to check. + * @return Returns true if the given [[FieldType]] is of a supported type. For supported types, + * see [[InterproceduralStringAnalysis.isSupportedType(String)]]. */ def isSupportedType(v: V): Boolean = if (v.value.isPrimitiveValue) { - val primTypeName = v.value.asPrimitiveValue.primitiveType.toJava - primTypeName == "short" || primTypeName == "int" || primTypeName == "float" || - primTypeName == "double" + isSupportedType(v.value.asPrimitiveValue.primitiveType.toJava) } else { try { - v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava == "java.lang.String" + isSupportedType(v.value.verificationTypeInfo.asObjectVariableInfo.clazz.toJava) } catch { case _: Exception ⇒ false } } + /** + * Determines whether a given [[FieldType]] element is supported by the string analysis. + * + * @param fieldType The element to check. + * @return Returns true if the given [[FieldType]] is of a supported type. For supported types, + * see [[InterproceduralStringAnalysis.isSupportedType(String)]]. + */ + def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) + } sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { From 39e00052ffd925ae505facfa3b6f7252650b0b34 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Mar 2019 17:12:48 +0100 Subject: [PATCH 206/316] Improved handling of declared methods. --- .../InterproceduralStringAnalysis.scala | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index cd9b84a297..14fa1166bd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -15,7 +15,6 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.value.ValueInformation -import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis @@ -77,16 +76,12 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { - private var declaredMethods: DeclaredMethods = _ + private val declaredMethods = project.get(DeclaredMethodsKey) def analyze(data: P): ProperPropertyComputationResult = { val state = InterproceduralComputationState(data) - declaredMethods = project.get(DeclaredMethodsKey) - // TODO: Is there a way to get the declared method in constant time? - val dm = declaredMethods.declaredMethods.find { dm ⇒ - dm.name == data._2.name && - dm.declaringClassType.toJava == data._2.classFile.thisType.toJava - }.get + // TODO: Make the entity of the analysis take a declared method instead of a method + val dm = declaredMethods(data._2) val tacaiEOptP = ps(data._2, TACAI.key) if (tacaiEOptP.hasUBP) { @@ -157,15 +152,7 @@ class InterproceduralStringAnalysis( } if (requiresCallersInfo) { - val declaredMethods = project.get(DeclaredMethodsKey).declaredMethods - val dm = declaredMethods.find { dm ⇒ - // TODO: What is a better / more robust way to compare methods (a check with == did - // not produce the expected behavior)? - dm.name == state.entity._2.name && - dm.declaringClassType.toJava == state.entity._2.classFile.thisType.toJava && - dm.definedMethod.descriptor.parameterTypes.length == - state.entity._2.descriptor.parameterTypes.length - }.get + val dm = declaredMethods(state.entity._2) val callersEOptP = ps(dm, CallersProperty.key) if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub @@ -758,7 +745,7 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule * Executor for the lazy analysis. */ object LazyInterproceduralStringAnalysis - extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData From a479bcdb047fcf20bc47d6bd7b1ee1ac4c550117 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 1 Mar 2019 20:12:19 +0100 Subject: [PATCH 207/316] Simplified the condition whether to retrieve callers information or not. --- .../string_analysis/InterproceduralTestMethods.java | 2 +- .../InterproceduralStringAnalysis.scala | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index de613aa632..d0bc3be5bf 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -219,7 +219,7 @@ public void knownHierarchyInstanceTest() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(Hello|Hello World)" + expectedStrings = "(Hello World|Hello)" ) }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 14fa1166bd..3b4945e981 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -142,14 +142,11 @@ class InterproceduralStringAnalysis( } // In case a parameter is required for approximating a string, retrieve callers information // (but only once and only if the expressions is not a local string) - val requiresCallersInfo = if (state.entity._1.definedBy.exists(_ < 0)) { - state.entity._2.parameterTypes.length > 0 && - state.entity._2.parameterTypes.exists { - InterproceduralStringAnalysis.isSupportedType - } && state.callers == null && state.params.isEmpty - } else { - !InterpretationHandler.isStringConstExpression(stmts(defSites.head).asAssignment.expr) + val hasSupportedParamType = state.entity._2.parameterTypes.exists { + InterproceduralStringAnalysis.isSupportedType } + val requiresCallersInfo = state.callers == null && state.params.isEmpty && + (state.entity._1.definedBy.exists(_ < 0) || hasSupportedParamType) if (requiresCallersInfo) { val dm = declaredMethods(state.entity._2) From 9a6acc89b816c07157d231064bf49c6afe55c5b5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 2 Mar 2019 17:02:44 +0100 Subject: [PATCH 208/316] The path transformer may need to update the fpe2sci map. --- .../string_analysis/InterproceduralStringAnalysis.scala | 8 ++++---- .../string_analysis/IntraproceduralStringAnalysis.scala | 2 +- .../string_analysis/preprocessing/PathTransformer.scala | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 3b4945e981..fa9c145ef9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -222,7 +222,7 @@ class InterproceduralStringAnalysis( } else { if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci.toMap + state.computedLeanPath, state.fpe2sci ).reduce(true) } } @@ -243,7 +243,7 @@ class InterproceduralStringAnalysis( if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci.toMap + state.computedLeanPath, state.fpe2sci ).reduce(true) } // No need to cover the else branch: interimResults.nonEmpty => dependees were added to @@ -426,8 +426,8 @@ class InterproceduralStringAnalysis( */ private def computeFinalResult(state: InterproceduralComputationState): Result = { finalizePreparations(state.computedLeanPath, state, state.iHandler) - val finalSci = new PathTransformer(null).pathToStringTree( - state.computedLeanPath, state.fpe2sci.toMap, resetExprHandler = false + val finalSci = new PathTransformer(state.iHandler).pathToStringTree( + state.computedLeanPath, state.fpe2sci, resetExprHandler = false ).reduce(true) InterproceduralStringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(finalSci)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index 2daac06148..96adf48aa1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -188,7 +188,7 @@ class IntraproceduralStringAnalysis( if (remDependees.isEmpty) { val interpretationHandler = IntraproceduralInterpretationHandler(state.tac) val finalSci = new PathTransformer(interpretationHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci.map { case (k, v) ⇒ (k, ListBuffer(v)) }.toMap + state.computedLeanPath, state.fpe2sci.map { case (k, v) ⇒ (k, ListBuffer(v)) } ).reduce(true) Result(data, StringConstancyProperty(finalSci)) } else { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index f3016310ce..e30bcd7815 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -2,6 +2,7 @@ package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable.ListBuffer +import scala.collection.mutable.Map import org.opalj.fpcf.Result import org.opalj.br.fpcf.properties.properties.StringTree @@ -40,7 +41,9 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) } else { val r = interpretationHandler.processDefSite(fpe.element).asInstanceOf[Result] - r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + fpe2Sci(fpe.element) = ListBuffer(sci) + sci } if (sci.isTheNeutralElement) { None From 3b251a640c5af69e6637d58e4760156aace701d5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Mar 2019 09:05:02 +0100 Subject: [PATCH 209/316] Refined the handling of functions. --- .../InterproceduralComputationState.scala | 2 +- .../InterproceduralStringAnalysis.scala | 21 ++++--- .../AbstractStringInterpreter.scala | 7 ++- ...ceduralStaticFunctionCallInterpreter.scala | 17 +++-- ...alFunctionCallPreparationInterpreter.scala | 62 ++++++++++--------- 5 files changed, 60 insertions(+), 49 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 2699957803..2f1fdbbd88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -115,7 +115,7 @@ case class InterproceduralComputationState(entity: P) { * During the process of updating the [[nonFinalFunctionArgs]] map, it is necessary to find out * to which function an entity belongs. We use the following map to do this in constant time. */ - val entity2Function: mutable.Map[P, FunctionCall[V]] = mutable.Map() + val entity2Function: mutable.Map[P, ListBuffer[FunctionCall[V]]] = mutable.Map() /** * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index fa9c145ef9..3324107172 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -361,14 +361,21 @@ class InterproceduralStringAnalysis( p.stringConstancyInformation ) // Update the state - val func = state.entity2Function(resultEntity) - val pos = state.nonFinalFunctionArgsPos(func)(resultEntity) - val result = Result(resultEntity, p) - state.nonFinalFunctionArgs(func)(pos._1)(pos._2)(pos._3) = result - state.entity2Function.remove(resultEntity) + state.entity2Function(resultEntity).foreach { f ⇒ + val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) + val result = Result(resultEntity, p) + state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result + state.entity2Function.remove(resultEntity) + } // Continue only after all necessary function parameters are evaluated if (state.entity2Function.nonEmpty) { - return determinePossibleStrings(state) + return InterimResult( + inputData, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) } else { // We could try to determine a final result before all function // parameter information are available, however, this will @@ -377,7 +384,7 @@ class InterproceduralStringAnalysis( // information are available state.entity2Function.clear() if (!computeResultsForPath(state.computedLeanPath, state)) { - determinePossibleStrings(state) + return determinePossibleStrings(state) } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 407508bdc3..0f8813c0ff 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -134,7 +134,7 @@ abstract class AbstractStringInterpreter( iHandler: InterproceduralInterpretationHandler, funCall: FunctionCall[V], functionArgsPos: NonFinalFunctionArgsPos, - entity2function: mutable.Map[P, FunctionCall[V]] + entity2function: mutable.Map[P, ListBuffer[FunctionCall[V]]] ): NonFinalFunctionArgs = params.zipWithIndex.map { case (nextParamList, outerIndex) ⇒ nextParamList.zipWithIndex.map { @@ -149,7 +149,10 @@ abstract class AbstractStringInterpreter( } val e = interim.eps.e.asInstanceOf[P] functionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) - entity2function(e) = funCall + if (!entity2function.contains(e)) { + entity2function(e) = ListBuffer() + } + entity2function(e).append(funCall) } r }.to[ListBuffer] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index eda5a2fb8a..70797905d1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -72,11 +72,10 @@ class InterproceduralStaticFunctionCallInterpreter( m.name == instr.name && m.declaringClassType == instr.declaringClass) }.keys - // Collect all parameters; either from the state, if the interpretation of instr was started + // Collect all parameters; either from the state if the interpretation of instr was started // before (in this case, the assumption is that all parameters are fully interpreted) or // start a new interpretation - val isFunctionArgsPreparationDone = state.nonFinalFunctionArgs.contains(instr) - val params = if (isFunctionArgsPreparationDone) { + val params = if (state.nonFinalFunctionArgs.contains(instr)) { state.nonFinalFunctionArgs(instr) } else { evaluateParameters( @@ -88,17 +87,15 @@ class InterproceduralStaticFunctionCallInterpreter( ) } // Continue only when all parameter information are available - if (!isFunctionArgsPreparationDone) { - val nonFinalResults = getNonFinalParameters(params) - if (nonFinalResults.nonEmpty) { - state.nonFinalFunctionArgs(instr) = params - return nonFinalResults.head - } + val nonFinalResults = getNonFinalParameters(params) + if (nonFinalResults.nonEmpty) { + state.nonFinalFunctionArgs(instr) = params + return nonFinalResults.head } + state.nonFinalFunctionArgs.remove(instr) state.nonFinalFunctionArgsPos.remove(instr) val evaluatedParams = convertEvaluatedParameters(params) - if (tac.isDefined) { // TAC available => Get return UVar and start the string analysis val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 07c8d1f7b4..d7a37a9903 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -125,8 +125,7 @@ class VirtualFunctionCallPreparationInterpreter( // Collect all parameters; either from the state, if the interpretation of instr was started // before (in this case, the assumption is that all parameters are fully interpreted) or // start a new interpretation - val isFunctionArgsPreparationDone = state.nonFinalFunctionArgs.contains(instr) - val params = if (isFunctionArgsPreparationDone) { + val params = if (state.nonFinalFunctionArgs.contains(instr)) { state.nonFinalFunctionArgs(instr) } else { evaluateParameters( @@ -138,41 +137,46 @@ class VirtualFunctionCallPreparationInterpreter( ) } // Continue only when all parameter information are available - if (!isFunctionArgsPreparationDone) { - val nonFinalResults = getNonFinalParameters(params) - if (nonFinalResults.nonEmpty) { - state.nonFinalFunctionArgs(instr) = params - return nonFinalResults.head - } + val nonFinalResults = getNonFinalParameters(params) + if (nonFinalResults.nonEmpty) { + state.nonFinalFunctionArgs(instr) = params + return nonFinalResults.head } + state.nonFinalFunctionArgs.remove(instr) state.nonFinalFunctionArgsPos.remove(instr) val evaluatedParams = convertEvaluatedParameters(params) - val results = methods.map { nextMethod ⇒ val tac = getTACAI(ps, nextMethod, state) if (tac.isDefined) { - // TAC available => Get return UVar and start the string analysis - val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get - val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar - val entity = (uvar, nextMethod) + // It might be that a function has no return value, e. g., in case it is guaranteed + // to throw an exception (see, e.g., + // com.sun.org.apache.xpath.internal.objects.XRTreeFragSelectWrapper#str) + if (!tac.get.stmts.exists(_.isInstanceOf[ReturnValue[V]])) { + Result(instr, StringConstancyProperty.lb) + } else { + // TAC and return available => Get return UVar and start the string analysis + val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get + val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + val entity = (uvar, nextMethod) - InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) - val eps = ps(entity, StringConstancyProperty.key) - eps match { - case r: Result ⇒ - state.appendResultToFpe2Sci(defSite, r) - r - case _ ⇒ - state.dependees = eps :: state.dependees - state.var2IndexMapping(uvar) = defSite - InterimResult( - entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - List(), - c - ) + InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case r: Result ⇒ + state.appendResultToFpe2Sci(defSite, r) + r + case _ ⇒ + state.dependees = eps :: state.dependees + state.var2IndexMapping(uvar) = defSite + InterimResult( + entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + List(), + c + ) + } } } else { // No TAC => Register dependee and continue From ce1529b2fd3ff1e89bc2fa78c172d3fa985d8ae1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Mar 2019 19:20:18 +0100 Subject: [PATCH 210/316] Refined the procedure to determine whether callers information are required. --- .../InterproceduralTestMethods.java | 2 +- .../InterproceduralStringAnalysis.scala | 129 ++++++++++++++---- .../InterpretationHandler.scala | 13 +- .../ArrayPreparationInterpreter.scala | 9 +- .../finalizer/ArrayFinalizer.scala | 4 +- 5 files changed, 121 insertions(+), 36 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index d0bc3be5bf..de613aa632 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -219,7 +219,7 @@ public void knownHierarchyInstanceTest() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(Hello World|Hello)" + expectedStrings = "(Hello|Hello World)" ) }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 3324107172..6361393e30 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -37,7 +37,6 @@ import org.opalj.tac.ExprStmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.FlatPathElement -import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath import org.opalj.tac.Assignment import org.opalj.tac.DUVar @@ -46,6 +45,11 @@ import org.opalj.tac.MethodCall import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType +import org.opalj.tac.ArrayLoad +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter +import org.opalj.tac.BinaryExpr +import org.opalj.tac.Expr /** * InterproceduralStringAnalysis processes a read operation of a string variable at a program @@ -142,11 +146,38 @@ class InterproceduralStringAnalysis( } // In case a parameter is required for approximating a string, retrieve callers information // (but only once and only if the expressions is not a local string) - val hasSupportedParamType = state.entity._2.parameterTypes.exists { - InterproceduralStringAnalysis.isSupportedType + val hasCallersOrParamInfo = state.callers == null && state.params.isEmpty + val requiresCallersInfo = if (defSites.exists(_ < 0)) { + if (InterpretationHandler.isStringConstExpression(uvar)) { + state.computedLeanPath = computeLeanPathForStringConst(uvar) + hasCallersOrParamInfo + } else { + // StringBuilders as parameters are currently not evaluated + return Result(state.entity, StringConstancyProperty.lb) + } + } else { + val call = stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val (leanPath, hasInitDefSites) = computeLeanPathForStringBuilder( + uvar, state.tac + ) + if (!hasInitDefSites) { + return Result(state.entity, StringConstancyProperty.lb) + } + state.computedLeanPath = leanPath + val hasSupportedParamType = state.entity._2.parameterTypes.exists { + InterproceduralStringAnalysis.isSupportedType + } + if (hasSupportedParamType) { + hasParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) + } else { + !hasCallersOrParamInfo + } + } else { + state.computedLeanPath = computeLeanPathForStringConst(uvar) + !hasCallersOrParamInfo + } } - val requiresCallersInfo = state.callers == null && state.params.isEmpty && - (state.entity._1.definedBy.exists(_ < 0) || hasSupportedParamType) if (requiresCallersInfo) { val dm = declaredMethods(state.entity._2) @@ -190,23 +221,12 @@ class InterproceduralStringAnalysis( var sci = StringConstancyProperty.lb.stringConstancyInformation // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { - state.computedLeanPath = Path(List(FlatPathElement(defSites.head))) val r = state.iHandler.processDefSite(defSites.head, state.params.toList) return Result(state.entity, StringConstancyProperty.extractFromPPCR(r)) } - val pathFinder: AbstractPathFinder = new WindowPathFinder(state.tac.cfg) val call = stmts(defSites.head).asAssignment.expr if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val initDefSites = InterpretationHandler.findDefSiteOfInit(uvar, stmts) - // initDefSites empty => String{Builder,Buffer} from method parameter is to be evaluated - if (initDefSites.isEmpty) { - return Result(state.entity, StringConstancyProperty.lb) - } - - val paths = pathFinder.findPaths(initDefSites, uvar.definedBy.head) - state.computedLeanPath = paths.makeLeanPath(uvar, stmts) - // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(state.computedLeanPath, stmts, uvar) if (dependentVars.nonEmpty) { @@ -228,19 +248,6 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - state.computedLeanPath = if (defSites.length == 1) { - // Trivial case for just one element - Path(List(FlatPathElement(defSites.head))) - } else { - // For > 1 definition sites, create a nest path element with |defSites| many - // children where each child is a NestPathElement(FlatPathElement) - val children = ListBuffer[SubPath]() - defSites.foreach { ds ⇒ - children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) - } - Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) - } - if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci @@ -562,6 +569,70 @@ class InterproceduralStringAnalysis( hasFinalResult } + /** + * This function computes the lean path for a [[DUVar]] which is required to be a string + * expressions. + */ + private def computeLeanPathForStringConst(duvar: V): Path = { + val defSites = duvar.definedBy.toArray.sorted + if (defSites.length == 1) { + // Trivial case for just one element + Path(List(FlatPathElement(defSites.head))) + } else { + // For > 1 definition sites, create a nest path element with |defSites| many + // children where each child is a NestPathElement(FlatPathElement) + val children = ListBuffer[SubPath]() + defSites.foreach { ds ⇒ + children.append(NestedPathElement(ListBuffer(FlatPathElement(ds)), None)) + } + Path(List(NestedPathElement(children, Some(NestedPathType.CondWithAlternative)))) + } + } + + /** + * This function computes the lean path for a [[DUVar]] which is required to stem from a + * `String{Builder, Buffer}#toString()` call. For this, the `tac` of the method, in which + * `duvar` resides, is required. + * This function then returns a pair of values: The first value is the computed lean path and + * the second value indicates whether the String{Builder, Buffer} has initialization sites + * within the method stored in `tac`. If it has no initialization sites, it returns + * `(null, false)` and otherwise `(computed lean path, true)`. + */ + private def computeLeanPathForStringBuilder( + duvar: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + ): (Path, Boolean) = { + val pathFinder: AbstractPathFinder = new WindowPathFinder(tac.cfg) + val initDefSites = InterpretationHandler.findDefSiteOfInit(duvar, tac.stmts) + if (initDefSites.isEmpty) { + (null, false) + } else { + val paths = pathFinder.findPaths(initDefSites, duvar.definedBy.head) + (paths.makeLeanPath(duvar, tac.stmts), true) + } + } + + private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { + def hasExprParamUsage(expr: Expr[V]): Boolean = expr match { + case al: ArrayLoad[V] ⇒ + ArrayPreparationInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) + case duvar: V ⇒ duvar.definedBy.exists(_ < 0) + case fc: FunctionCall[V] ⇒ fc.params.exists(hasExprParamUsage) + case mc: MethodCall[V] ⇒ mc.params.exists(hasExprParamUsage) + case be: BinaryExpr[V] ⇒ hasExprParamUsage(be.left) || hasExprParamUsage(be.right) + case _ ⇒ false + } + + path.elements.exists { + case FlatPathElement(index) ⇒ stmts(index) match { + case Assignment(_, _, expr) ⇒ hasExprParamUsage(expr) + case ExprStmt(_, expr) ⇒ hasExprParamUsage(expr) + case _ ⇒ false + } + case NestedPathElement(subPath, _) ⇒ hasParamUsageAlongPath(Path(subPath.toList), stmts) + case _ ⇒ false + } + } + /** * Helper / accumulator function for finding dependees. For how dependees are detected, see * findDependentVars. Returns a list of pairs of DUVar and the index of the diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index e2b1cf2d32..e4b8ed242c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -100,7 +100,18 @@ object InterpretationHandler { * @return Returns `true` if the given expression is a string constant / literal and `false` * otherwise. */ - def isStringConstExpression(expr: Expr[V]): Boolean = expr.isStringConst + def isStringConstExpression(expr: Expr[V]): Boolean = if (expr.isStringConst) { + true + } else { + if (expr.isVar) { + val value = expr.asVar.value + value.isReferenceValue && value.asReferenceValue.upperTypeBound.exists { + _.toJava == "java.lang.String" + } + } else { + false + } + } /** * Checks whether an expression contains a call to [[StringBuilder#append]] or diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 115a8a5d2d..55ebc9ec4d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -53,7 +53,9 @@ class ArrayPreparationInterpreter( val results = ListBuffer[ProperPropertyComputationResult]() val defSites = instr.arrayRef.asVar.definedBy.toArray - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, cfg) + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( + instr, state.tac.stmts + ) allDefSites.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, r: Result) ⇒ @@ -99,12 +101,11 @@ object ArrayPreparationInterpreter { * to the given instruction. * * @param instr The [[ArrayLoad]] instruction to get the definition sites for. - * @param cfg The underlying control flow graph. + * @param stmts The set of statements to use. * @return Returns all definition sites associated with the array stores and array loads of the * given instruction. The result list is sorted in ascending order. */ - def getStoreAndLoadDefSites(instr: T, cfg: CFG[Stmt[V], TACStmts[V]]): List[Int] = { - val stmts = cfg.code.instructions + def getStoreAndLoadDefSites(instr: T, stmts: Array[Stmt[V]]): List[Int] = { val allDefSites = ListBuffer[Int]() val defSites = instr.arrayRef.asVar.definedBy.toArray diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala index 39c513b0b7..f94cc27e4a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala @@ -27,7 +27,9 @@ class ArrayFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites(instr, cfg) + val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( + instr, state.tac.stmts + ) state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( allDefSites.sorted.flatMap(state.fpe2sci(_)) )) From 8d277692ba6f98aa7da8c1055a2f2145a2e3f0fc Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 12:07:07 +0100 Subject: [PATCH 211/316] In some cases it was unnecessary to collect callers information as they were already present. --- .../InterproceduralTestMethods.java | 12 ++- .../InterproceduralStringAnalysis.scala | 79 ++++++++++++------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index de613aa632..5f2e123032 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -9,6 +9,7 @@ import javax.management.remote.rmi.RMIServer; import java.io.File; import java.io.FileNotFoundException; +import java.lang.reflect.Method; import java.util.Scanner; import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; @@ -219,7 +220,7 @@ public void knownHierarchyInstanceTest() { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(Hello|Hello World)" + expectedStrings = "(Hello World|Hello)" ) }) @@ -306,11 +307,16 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { ) }) - public void dependenciesWithinFinalizeTest(String s) { + public void dependenciesWithinFinalizeTest(String s, Class clazz) { String properName = s.length() == 1 ? s.substring(0, 1).toUpperCase() : getHelloWorld() + getRuntimeClassName(); String getterName = "get" + properName; - analyzeString(getterName); + Method m; + try { + m = clazz.getMethod(getterName); + System.out.println(m); + analyzeString(getterName); + } catch (NoSuchMethodException var13) {} } @StringDefinitionsCollection( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 6361393e30..cb558ce3fa 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -141,41 +141,43 @@ class InterproceduralStringAnalysis( ) } + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uvar, state.tac) + } + + var requiresCallersInfo = false if (state.params.isEmpty) { state.params = InterproceduralStringAnalysis.getParams(state.entity) } - // In case a parameter is required for approximating a string, retrieve callers information - // (but only once and only if the expressions is not a local string) - val hasCallersOrParamInfo = state.callers == null && state.params.isEmpty - val requiresCallersInfo = if (defSites.exists(_ < 0)) { - if (InterpretationHandler.isStringConstExpression(uvar)) { - state.computedLeanPath = computeLeanPathForStringConst(uvar) - hasCallersOrParamInfo - } else { - // StringBuilders as parameters are currently not evaluated - return Result(state.entity, StringConstancyProperty.lb) - } - } else { - val call = stmts(defSites.head).asAssignment.expr - if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { - val (leanPath, hasInitDefSites) = computeLeanPathForStringBuilder( - uvar, state.tac - ) - if (!hasInitDefSites) { + if (state.params.isEmpty) { + // In case a parameter is required for approximating a string, retrieve callers information + // (but only once and only if the expressions is not a local string) + val hasCallersOrParamInfo = state.callers == null && state.params.isEmpty + requiresCallersInfo = if (defSites.exists(_ < 0)) { + if (InterpretationHandler.isStringConstExpression(uvar)) { + hasCallersOrParamInfo + } else { + // StringBuilders as parameters are currently not evaluated return Result(state.entity, StringConstancyProperty.lb) } - state.computedLeanPath = leanPath - val hasSupportedParamType = state.entity._2.parameterTypes.exists { - InterproceduralStringAnalysis.isSupportedType - } - if (hasSupportedParamType) { - hasParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) + } else { + val call = stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val (_, hasInitDefSites) = computeLeanPathForStringBuilder(uvar, state.tac) + if (!hasInitDefSites) { + return Result(state.entity, StringConstancyProperty.lb) + } + val hasSupportedParamType = state.entity._2.parameterTypes.exists { + InterproceduralStringAnalysis.isSupportedType + } + if (hasSupportedParamType) { + hasParamUsageAlongPath(state.computedLeanPath, state.tac.stmts) + } else { + !hasCallersOrParamInfo + } } else { !hasCallersOrParamInfo } - } else { - state.computedLeanPath = computeLeanPathForStringConst(uvar) - !hasCallersOrParamInfo } } @@ -372,7 +374,7 @@ class InterproceduralStringAnalysis( val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) val result = Result(resultEntity, p) state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result - state.entity2Function.remove(resultEntity) + state.entity2Function.remove(resultEntity) // TODO: Is that correct? (rather remove only the function from the list) } // Continue only after all necessary function parameters are evaluated if (state.entity2Function.nonEmpty) { @@ -569,6 +571,27 @@ class InterproceduralStringAnalysis( hasFinalResult } + /** + * This function is a wrapper function for [[computeLeanPathForStringConst]] and + * [[computeLeanPathForStringBuilder]]. + */ + private def computeLeanPath( + duvar: V, tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + ): Path = { + val defSites = duvar.definedBy.toArray.sorted + if (defSites.head < 0) { + computeLeanPathForStringConst(duvar) + } else { + val call = tac.stmts(defSites.head).asAssignment.expr + if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { + val (leanPath, _) = computeLeanPathForStringBuilder(duvar, tac) + leanPath + } else { + computeLeanPathForStringConst(duvar) + } + } + } + /** * This function computes the lean path for a [[DUVar]] which is required to be a string * expressions. From b2044387a09ad7ac5045c479f54c9aa6221f0f4e Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 15:08:07 +0100 Subject: [PATCH 212/316] Added handling for the default case. --- .../finalizer/VirtualFunctionCallFinalizer.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 603c342432..dde6f7c5e8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -5,6 +5,7 @@ import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -30,7 +31,9 @@ class VirtualFunctionCallFinalizer( instr.name match { case "append" ⇒ finalizeAppend(instr, defSite) case "toString" ⇒ finalizeToString(instr, defSite) - case _ ⇒ + case _ ⇒ state.appendToFpe2Sci( + defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true + ) } } From 5c2a104f8a9151affe1225fb06a06daa2858ff4a Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 19:51:10 +0100 Subject: [PATCH 213/316] Refined the finalization handling. --- ...InterproceduralInterpretationHandler.scala | 46 +++++++++++++------ .../finalizer/ArrayFinalizer.scala | 8 ++++ .../NonVirtualMethodCallFinalizer.scala | 27 ++++++++--- .../VirtualFunctionCallFinalizer.scala | 10 +++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index d0b839a10e..a4d3c7ab9a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -79,10 +79,7 @@ class InterproceduralInterpretationHandler( (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset)) { return Result(e, StringConstancyProperty.lb) } else if (defSite < 0) { - val paramPos = Math.abs(defSite + 2) - val paramScis = params.map(_(paramPos)).distinct - val finalParamSci = StringConstancyInformation.reduceMultiple(paramScis) - return Result(e, StringConstancyProperty(finalParamSci)) + return Result(e, StringConstancyProperty(getParam(params, defSite))) } else if (processedDefSites.contains(defSite)) { return Result(e, StringConstancyProperty.getNeutralElement) } @@ -192,19 +189,40 @@ class InterproceduralInterpretationHandler( } } + /** + * This function takes parameters and a definition site and extracts the desired parameter from + * the given list of parameters. Note that `defSite` is required to be <= -2. + */ + private def getParam( + params: Seq[Seq[StringConstancyInformation]], defSite: Int + ): StringConstancyInformation = { + val paramPos = Math.abs(defSite + 2) + val paramScis = params.map(_(paramPos)).distinct + StringConstancyInformation.reduceMultiple(paramScis) + } + + /** + * Finalized a given definition state. + */ def finalizeDefSite( defSite: Int, state: InterproceduralComputationState ): Unit = { - stmts(defSite) match { - case nvmc: NonVirtualMethodCall[V] ⇒ - new NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) - case Assignment(_, _, al: ArrayLoad[V]) ⇒ - new ArrayFinalizer(state, cfg).finalizeInterpretation(al, defSite) - case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ - new VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ - new VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case _ ⇒ + if (defSite < 0) { + state.appendToFpe2Sci(defSite, getParam(state.params, defSite), reset = true) + } else { + stmts(defSite) match { + case nvmc: NonVirtualMethodCall[V] ⇒ + NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) + case Assignment(_, _, al: ArrayLoad[V]) ⇒ + ArrayFinalizer(state, cfg).finalizeInterpretation(al, defSite) + case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ + VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) + case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ + VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) + case _ ⇒ state.appendToFpe2Sci( + defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true + ) + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala index f94cc27e4a..5ae15c0de4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala @@ -36,3 +36,11 @@ class ArrayFinalizer( } } + +object ArrayFinalizer { + + def apply( + state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + ): ArrayFinalizer = new ArrayFinalizer(state, cfg) + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala index 37ca70ef82..d30b6e53c2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NonVirtualMethodCallFinalizer.scala @@ -2,6 +2,7 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualMethodCall import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -21,12 +22,26 @@ class NonVirtualMethodCallFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } - state.appendToFpe2Sci( - defSite, - StringConstancyInformation.reduceMultiple(scis.flatten.toList), - reset = true - ) + val toAppend = if (instr.params.nonEmpty) { + instr.params.head.asVar.definedBy.toArray.foreach { ds ⇒ + if (!state.fpe2sci.contains(ds)) { + state.iHandler.finalizeDefSite(ds, state) + } + } + val scis = instr.params.head.asVar.definedBy.toArray.sorted.map { state.fpe2sci } + StringConstancyInformation.reduceMultiple(scis.flatten.toList) + } else { + StringConstancyProperty.lb.stringConstancyInformation + } + state.appendToFpe2Sci(defSite, toAppend, reset = true) } } + +object NonVirtualMethodCallFinalizer { + + def apply( + state: InterproceduralComputationState + ): NonVirtualMethodCallFinalizer = new NonVirtualMethodCallFinalizer(state) + +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index dde6f7c5e8..85848f7217 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -31,7 +31,7 @@ class VirtualFunctionCallFinalizer( instr.name match { case "append" ⇒ finalizeAppend(instr, defSite) case "toString" ⇒ finalizeToString(instr, defSite) - case _ ⇒ state.appendToFpe2Sci( + case _ ⇒ state.appendToFpe2Sci( defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true ) } @@ -103,3 +103,11 @@ class VirtualFunctionCallFinalizer( } } + +object VirtualFunctionCallFinalizer { + + def apply( + state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + ): VirtualFunctionCallFinalizer = new VirtualFunctionCallFinalizer(state, cfg) + +} From dc6e3589c3a7ada479d3dfa7a76fe0185586bcb5 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 22:15:08 +0100 Subject: [PATCH 214/316] Added a test case which, currently, does not work, as there is a cyclic dependency. --- .../InterproceduralTestMethods.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 5f2e123032..f02ed5aeee 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -364,6 +364,19 @@ public void noCallersInformationRequiredTest(String s) { analyzeString("java.lang.String"); } +// @StringDefinitionsCollection( +// value = "a case where no callers information need to be computed", +// stringDefinitions = { +// expectedLevel = CONSTANT, +// expectedStrings = "value" +// ) +// }) +// public String cyclicDependencyTest(String s) { +// String value = getProperty(s); +// analyzeString(value); +// return value; +// } + /** * Necessary for the callerWithFunctionParameterTest. */ @@ -403,4 +416,12 @@ private String addQuestionMark(String s) { return s + "?"; } +// private String getProperty(String name) { +// if (name == null) { +// return cyclicDependencyTest("default"); +// } else { +// return "value"; +// } +// } + } From b3a5a8dd3d8ac118bc9c5909db0e92a34f69a307 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 4 Mar 2019 22:15:56 +0100 Subject: [PATCH 215/316] Refined the handling of NonVirtualFunctionCalls and modified some comments. --- .../InterproceduralInterpretationHandler.scala | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index a4d3c7ab9a..c34ff28422 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -124,7 +124,8 @@ class InterproceduralInterpretationHandler( ).interpret(expr, defSite) // In case no final result could be computed, remove this def site from the list of // processed def sites to make sure that is can be compute again (when all final - // results are available) + // results are available); we use nonFinalFunctionArgs because if it does not + // contain expr, it can be finalized later on without processing the function again if (state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -133,9 +134,6 @@ class InterproceduralInterpretationHandler( val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - // In case no final result could be computed, remove this def site from the list of - // processed def sites to make sure that is can be compute again (when all final - // results are available) if (state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -145,18 +143,19 @@ class InterproceduralInterpretationHandler( state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) result case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ - new InterproceduralNonVirtualFunctionCallInterpreter( + val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) + if (state.nonFinalFunctionArgs.contains(expr)) { + processedDefSites.remove(defSite) + } + r case Assignment(_, _, expr: GetField[V]) ⇒ new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ val r = new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) - // In case no final result could be computed, remove this def site from the list of - // processed def sites to make sure that is can be compute again (when all final - // results are available) if (state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } @@ -165,9 +164,6 @@ class InterproceduralInterpretationHandler( val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - // In case no final result could be computed, remove this def site from the list of - // processed def sites to make sure that is can be compute again (when all final - // results are available) if (!r.isInstanceOf[Result]) { processedDefSites.remove(defSite) } From 0ca4223fd8053dafd521c416a4f2e79b288df684 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 09:03:59 +0100 Subject: [PATCH 216/316] Refined a condition. --- .../preprocessing/AbstractPathFinder.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 9c11af824d..6e456d49a6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -392,15 +392,17 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { startEndPairs.append((popped, nextBlock - 1)) stack.push(nextBlock) } else { - endSite = nextBlock - 1 - if (endSite == start) { - endSite = end - } // The following is necessary to not exceed bounds (might be the case within a - // try block for example) - else if (endSite > end) { - endSite = end + if (popped <= end) { + endSite = nextBlock - 1 + if (endSite == start) { + endSite = end + } // The following is necessary to not exceed bounds (might be the case within a + // try block for example) + else if (endSite > end) { + endSite = end + } + startEndPairs.append((popped, endSite)) } - startEndPairs.append((popped, endSite)) } } From a52f5da99b7ac31da87314364091c880fccd4270 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 09:19:11 +0100 Subject: [PATCH 217/316] Changed a parameter the "findPaths" function is called with. --- .../string_analysis/InterproceduralStringAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index cb558ce3fa..506270bb24 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -629,7 +629,7 @@ class InterproceduralStringAnalysis( if (initDefSites.isEmpty) { (null, false) } else { - val paths = pathFinder.findPaths(initDefSites, duvar.definedBy.head) + val paths = pathFinder.findPaths(initDefSites, duvar.definedBy.toArray.max) (paths.makeLeanPath(duvar, tac.stmts), true) } } From a4388955f7256cf5bd6f68d088986aa7953d06e0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:01:54 +0100 Subject: [PATCH 218/316] Modified a condition. --- .../interprocedural/InterproceduralInterpretationHandler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index c34ff28422..5ec51fbece 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -164,7 +164,7 @@ class InterproceduralInterpretationHandler( val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result]) { + if (state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } r From 8bc43909121c7e4677c9cd4df8e51063d0fdcdfb Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:04:10 +0100 Subject: [PATCH 219/316] Had to refine when a result is added to the fpe2sci map (adding the neutral element led to false results). --- .../VirtualFunctionCallPreparationInterpreter.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index d7a37a9903..d0b80dc94a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -271,14 +271,21 @@ class VirtualFunctionCallPreparationInterpreter( val allResults = defSites.map(ds ⇒ (ds, exprHandler.processDefSite(ds, params))) val finalResults = allResults.filter(_._2.isInstanceOf[Result]) + val finalResultsWithoutNeutralElements = finalResults.filter { + case (_, Result(r)) ⇒ + val p = r.p.asInstanceOf[StringConstancyProperty] + !p.stringConstancyInformation.isTheNeutralElement + case _ ⇒ false + } val intermediateResults = allResults.filter(!_._2.isInstanceOf[Result]) - // Extend the state by the final results - finalResults.foreach { next ⇒ + // Extend the state by the final results not being the neutral elements (they might need to + // be finalized later) + finalResultsWithoutNeutralElements.foreach { next ⇒ state.appendResultToFpe2Sci(next._1, next._2.asInstanceOf[Result]) } - if (allResults.length == finalResults.length) { + if (intermediateResults.isEmpty) { finalResults.map(_._2).toList } else { List(intermediateResults.head._2) From ebedcbb7f2d776041471971c7647691dbf03559d Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:04:31 +0100 Subject: [PATCH 220/316] Added a test case which did not work previously but does so now because of the last commits. --- .../InterproceduralTestMethods.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index f02ed5aeee..c6b445e5c0 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -364,6 +364,33 @@ public void noCallersInformationRequiredTest(String s) { analyzeString("java.lang.String"); } + @StringDefinitionsCollection( + value = "a case taken from com.sun.prism.impl.ps.BaseShaderContext#getPaintShader " + + "and slightly adapted", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "Hello, World_paintname((_PAD|_REFLECT|_REPEAT)?)?" + + "(_AlphaTest)?" + ) + }) + public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alphaTest) { + String shaderName = getHelloWorld() + "_" + "paintname"; + if (getPaintType) { + if (spreadMethod == 0) { + shaderName = shaderName + "_PAD"; + } else if (spreadMethod == 1) { + shaderName = shaderName + "_REFLECT"; + } else if (spreadMethod == 2) { + shaderName = shaderName + "_REPEAT"; + } + } + if (alphaTest) { + shaderName = shaderName + "_AlphaTest"; + } + analyzeString(shaderName); + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { From 8831a1e8d057ef50516b23068fa2ac22c6b2c029 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 10:31:52 +0100 Subject: [PATCH 221/316] Slightly refined the array finalizer in the way to finalize required data before its own finalization. --- .../interprocedural/finalizer/ArrayFinalizer.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala index 5ae15c0de4..19266cf7b4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala @@ -30,6 +30,13 @@ class ArrayFinalizer( val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( instr, state.tac.stmts ) + + allDefSites.foreach { ds ⇒ + if (!state.fpe2sci.contains(ds)) { + state.iHandler.finalizeDefSite(ds, state) + } + } + state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( allDefSites.sorted.flatMap(state.fpe2sci(_)) )) From f997787889e40ea42838f6a8c109f92c02998cf9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 13:20:04 +0100 Subject: [PATCH 222/316] Changed the handling when no TAC is available. --- .../InterproceduralStaticFunctionCallInterpreter.scala | 10 ++-------- .../VirtualFunctionCallPreparationInterpreter.scala | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 70797905d1..c4169e11fb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -119,14 +119,8 @@ class InterproceduralStaticFunctionCallInterpreter( ) } } else { - // No TAC => Register dependee and continue - InterimResult( - m, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + // No TAC (e.g., for native methods) + Result(instr, StringConstancyProperty.lb) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index d0b80dc94a..aabe57ba3e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -179,14 +179,8 @@ class VirtualFunctionCallPreparationInterpreter( } } } else { - // No TAC => Register dependee and continue - InterimResult( - nextMethod, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + // No TAC (e.g., for native methods) + Result(instr, StringConstancyProperty.lb) } } From ac868a09065932c108c47d0217aadd6523f0e595 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 13:20:33 +0100 Subject: [PATCH 223/316] Prevented the procedure from trying to elements that do not exist. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 6e456d49a6..1d0dc065fb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -98,7 +98,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // No goto available => Jump after next block var nextIf: Option[If[V]] = None var i = nextBlock - while (nextIf.isEmpty) { + while (i < cfg.code.instructions.length && nextIf.isEmpty) { cfg.code.instructions(i) match { case iff: If[V] ⇒ nextIf = Some(iff) @@ -107,7 +107,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } i += 1 } - endSite = nextIf.get.targetStmt + endSite = if (nextIf.isDefined) nextIf.get.targetStmt else { + stack.clear() + i - 1 + } } } } From ad5ac79ff24143f3d6698e8f7205ac5f31af7f29 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 13:59:27 +0100 Subject: [PATCH 224/316] Needed to add a case in order for the match to be exhaustive. --- .../string_analysis/interpretation/InterpretationHandler.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index e4b8ed242c..d719bb5d9b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -153,6 +153,7 @@ object InterpretationHandler { defSites.append(next) case vfc: VirtualFunctionCall[V] ⇒ stack.pushAll(vfc.receiver.asVar.definedBy.filter(_ >= 0).toArray) + case _ ⇒ // E.g., NullExpr } case _ ⇒ } From 8ef8efd7e3c49c303193254c959759c4a70383ad Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 15:53:38 +0100 Subject: [PATCH 225/316] Not only VirtualFunctionCalls might be involved in finding definition sites of an init => extended the support --- .../interpretation/InterpretationHandler.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index d719bb5d9b..b91c332527 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -173,9 +173,11 @@ object InterpretationHandler { def findDefSiteOfInit(duvar: V, stmts: Array[Stmt[V]]): List[Int] = { val defSites = ListBuffer[Int]() duvar.definedBy.foreach { ds ⇒ - defSites.appendAll( - findDefSiteOfInitAcc(stmts(ds).asAssignment.expr.asVirtualFunctionCall, stmts) - ) + defSites.appendAll(stmts(ds).asAssignment.expr match { + case vfc: VirtualFunctionCall[V] ⇒ findDefSiteOfInitAcc(vfc, stmts) + // The following case is, e.g., for {NonVirtual, Static}FunctionCalls + case _ ⇒ List(ds) + }) } defSites.distinct.sorted.toList } From adf6559b0f1516a76f1b39f0d53f1323f2337c99 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 15:55:58 +0100 Subject: [PATCH 226/316] The mapping from variable / entity to index might be a 1-to-n relation => Extended it (from a 1-to-1); a corresponding test case was added. --- .../InterproceduralTestMethods.java | 27 +++++++++++++++++++ .../InterproceduralComputationState.scala | 12 ++++++++- .../InterproceduralStringAnalysis.scala | 15 ++++++----- ...ralNonVirtualFunctionCallInterpreter.scala | 2 +- ...ceduralStaticFunctionCallInterpreter.scala | 2 +- ...alFunctionCallPreparationInterpreter.scala | 2 +- 6 files changed, 49 insertions(+), 11 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index c6b445e5c0..f3cf98c9dd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -1,6 +1,7 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string_analysis; +import com.sun.corba.se.impl.util.PackagePrefixChecker; import org.opalj.fpcf.fixtures.string_analysis.hierarchies.GreetingService; import org.opalj.fpcf.fixtures.string_analysis.hierarchies.HelloGreeting; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; @@ -10,6 +11,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.lang.reflect.Method; +import java.rmi.Remote; import java.util.Scanner; import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; @@ -391,6 +393,24 @@ public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alpha analyzeString(shaderName); } + @StringDefinitionsCollection( + value = "a case taken from com.sun.corba.se.impl.util.Utility#tieName and slightly " + + "adapted", + stringDefinitions = { + @StringDefinitions( + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "(\\w\\w_tie|\\w_tie)" + ) + }) + public String tieName(String var0, Remote remote) { + PackagePrefixChecker ppc = new PackagePrefixChecker(); + String s = PackagePrefixChecker.hasOffendingPrefix(tieNameForCompiler(var0)) ? + PackagePrefixChecker.packagePrefix() + tieNameForCompiler(var0) : + tieNameForCompiler(var0); + analyzeString(s); + return s; + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { @@ -419,6 +439,13 @@ public static String belongsToTheSameTestCase() { return getHelloWorld(); } + /** + * Necessary for the tieName test. + */ + private static String tieNameForCompiler(String var0) { + return var0 + "_tie"; + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 2f1fdbbd88..8d731157cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -61,7 +61,7 @@ case class InterproceduralComputationState(entity: P) { /** * A mapping from DUVar elements to the corresponding indices of the FlatPathElements */ - val var2IndexMapping: mutable.Map[V, Int] = mutable.Map() + val var2IndexMapping: mutable.Map[V, ListBuffer[Int]] = mutable.Map() /** * A mapping from values / indices of FlatPathElements to StringConstancyInformation @@ -142,4 +142,14 @@ case class InterproceduralComputationState(entity: P) { fpe2sci(defSite).append(sci) } + /** + * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. + */ + def appendToVar2IndexMapping(entity: V, defSite: Int): Unit = { + if (!var2IndexMapping.contains(entity)) { + var2IndexMapping(entity) = ListBuffer() + } + var2IndexMapping(entity).append(defSite) + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 506270bb24..8cf7eccdef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -234,7 +234,7 @@ class InterproceduralStringAnalysis( if (dependentVars.nonEmpty) { dependentVars.keys.foreach { nextVar ⇒ val toAnalyze = (nextVar, state.entity._2) - dependentVars.foreach { case (k, v) ⇒ state.var2IndexMapping(k) = v } + dependentVars.foreach { case (k, v) ⇒ state.appendToVar2IndexMapping(k, v) } val ep = propertyStore(toAnalyze, StringConstancyProperty.key) ep match { case FinalP(p) ⇒ return processFinalP(state, ep.e, p) @@ -365,10 +365,9 @@ class InterproceduralStringAnalysis( // If necessary, update parameter information of function calls if (state.entity2Function.contains(resultEntity)) { - state.appendToFpe2Sci( - state.var2IndexMapping(resultEntity._1), - p.stringConstancyInformation - ) + state.var2IndexMapping(resultEntity._1).foreach(state.appendToFpe2Sci( + _, p.stringConstancyInformation + )) // Update the state state.entity2Function(resultEntity).foreach { f ⇒ val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) @@ -461,7 +460,9 @@ class InterproceduralStringAnalysis( // Add mapping information (which will be used for computing the final result) val retrievedProperty = p.asInstanceOf[StringConstancyProperty] val currentSci = retrievedProperty.stringConstancyInformation - state.appendToFpe2Sci(state.var2IndexMapping(e.asInstanceOf[P]._1), currentSci) + state.var2IndexMapping(e.asInstanceOf[P]._1).foreach { + state.appendToFpe2Sci(_, currentSci) + } // No more dependees => Return the result for this analysis run state.dependees = state.dependees.filter(_.e != e) @@ -511,7 +512,7 @@ class InterproceduralStringAnalysis( if (InterproceduralStringAnalysis.isSupportedType(p.asVar)) { val paramEntity = (p.asVar, m.definedMethod) val eps = propertyStore(paramEntity, StringConstancyProperty.key) - state.var2IndexMapping(paramEntity._1) = paramIndex + state.appendToVar2IndexMapping(paramEntity._1, paramIndex) eps match { case FinalP(r) ⇒ state.params(methodIndex)(paramIndex) = r.stringConstancyInformation diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 39af2ded50..382cde247d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -69,7 +69,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( Result(e, p) case _ ⇒ state.dependees = eps :: state.dependees - state.var2IndexMapping(uvar) = defSite + state.appendToVar2IndexMapping(uvar, defSite) InterimResult( state.entity, StringConstancyProperty.lb, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index c4169e11fb..6493b1dc42 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -109,7 +109,7 @@ class InterproceduralStaticFunctionCallInterpreter( Result(e, p) case _ ⇒ state.dependees = eps :: state.dependees - state.var2IndexMapping(uvar) = defSite + state.appendToVar2IndexMapping(uvar, defSite) InterimResult( entity, StringConstancyProperty.lb, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index aabe57ba3e..2d0b827a6f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -168,7 +168,7 @@ class VirtualFunctionCallPreparationInterpreter( r case _ ⇒ state.dependees = eps :: state.dependees - state.var2IndexMapping(uvar) = defSite + state.appendToVar2IndexMapping(uvar, defSite) InterimResult( entity, StringConstancyProperty.lb, From 3f7b48ffe3c58f32a60a452642f8036ece026943 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Mar 2019 19:45:23 +0100 Subject: [PATCH 227/316] Changed the analysis to retrieve the TAC from the property store. --- .../IntraproceduralStringAnalysis.scala | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index 96adf48aa1..9427b818bf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -24,7 +24,6 @@ import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ExprStmt -import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler @@ -74,21 +73,29 @@ class IntraproceduralStringAnalysis( * have all required information ready for a final result. */ private case class ComputationState( - // The lean path that was computed - computedLeanPath: Path, - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - var2IndexMapping: mutable.Map[V, Int], - // A mapping from values of FlatPathElements to StringConstancyInformation - fpe2sci: mutable.Map[Int, StringConstancyInformation], - // The three-address code of the method in which the entity under analysis resides - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + // The lean path that was computed + computedLeanPath: Path, + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + var2IndexMapping: mutable.Map[V, Int], + // A mapping from values of FlatPathElements to StringConstancyInformation + fpe2sci: mutable.Map[Int, StringConstancyInformation], + // The three-address code of the method in which the entity under analysis resides + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ) def analyze(data: P): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val tacProvider = p.get(SimpleTACAIKey) - val tac = tacProvider(data._2) + val tacaiEOptP = ps(data._2, TACAI.key) + var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = null + if (tacaiEOptP.hasUBP) { + if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + return Result(data, StringConstancyProperty.lb) + } else { + tac = tacaiEOptP.ub.tac.get + } + } val cfg = tac.cfg val stmts = tac.stmts @@ -341,7 +348,7 @@ sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule * Executor for the lazy analysis. */ object LazyIntraproceduralStringAnalysis - extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData From f96796576b76098d04a272170da38e3c5305f97c Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 14:30:05 +0100 Subject: [PATCH 228/316] Started adding support for fields. --- .../InterproceduralTestMethods.java | 19 ++++++ .../InterproceduralStringAnalysis.scala | 10 +++- .../InterproceduralFieldInterpreter.scala | 59 ++++++++++++++++--- ...InterproceduralInterpretationHandler.scala | 33 +++++++---- 4 files changed, 98 insertions(+), 23 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index f3cf98c9dd..0c05ec5969 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -28,6 +28,8 @@ public class InterproceduralTestMethods { private static final String rmiServerImplStubClassName = RMIServer.class.getName() + "Impl_Stub"; + private String myField; + /** * {@see LocalTestMethods#analyzeString} */ @@ -411,6 +413,23 @@ public String tieName(String var0, Remote remote) { return s; } + @StringDefinitionsCollection( + value = "a case where a string field is read", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "(\\w|some value)" + ) + }) + public void fieldReadTest() { + myField = "some value"; + analyzeString(myField); + } + + private void belongsToFieldReadTest() { + myField = "another value"; + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 8cf7eccdef..770e81295f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -26,6 +26,7 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.FieldType import org.opalj.br.Method +import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement @@ -81,6 +82,7 @@ class InterproceduralStringAnalysis( ) extends FPCFAnalysis { private val declaredMethods = project.get(DeclaredMethodsKey) + private final val fieldAccessInformation = project.get(FieldAccessInformationKey) def analyze(data: P): ProperPropertyComputationResult = { val state = InterproceduralComputationState(data) @@ -137,7 +139,7 @@ class InterproceduralStringAnalysis( val tacProvider = p.get(SimpleTACAIKey) if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, state, continuation(state) + state.tac, ps, declaredMethods, fieldAccessInformation, state, continuation(state) ) } @@ -290,7 +292,11 @@ class InterproceduralStringAnalysis( eps.pk match { case TACAI.key ⇒ eps match { case FinalP(tac: TACAI) ⇒ - state.tac = tac.tac.get + // Set the TAC only once (the TAC might be requested for other methods, so this + // makes sure we do not overwrite the state's TAC) + if (state.tac == null) { + state.tac = tac.tac.get + } state.dependees = state.dependees.filter(_.e != eps.e) if (state.dependees.isEmpty) { determinePossibleStrings(state) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index b8aae85e9b..4513a74269 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -1,16 +1,21 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +import scala.collection.mutable.ListBuffer + +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees +import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetField -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis /** * The `InterproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this @@ -23,10 +28,12 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractString * @author Patrick Mell */ class InterproceduralFieldInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler, - callees: Callees -) extends AbstractStringInterpreter(cfg, exprHandler) { + state: InterproceduralComputationState, + exprHandler: InterproceduralInterpretationHandler, + ps: PropertyStore, + fieldAccessInformation: FieldAccessInformation, + c: ProperOnUpdateContinuation +) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { override type T = GetField[V] @@ -42,6 +49,40 @@ class InterproceduralFieldInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lb) + if (InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { + val results = ListBuffer[ProperPropertyComputationResult]() + fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { + case (m, pcs) ⇒ pcs.foreach { pc ⇒ + getTACAI(ps, m, state) match { + case Some(methodTac) ⇒ + val stmt = methodTac.stmts(methodTac.pcToIndex(pc)) + val entity = (stmt.asPutField.value.asVar, m) + val eps = ps(entity, StringConstancyProperty.key) + results.append(eps match { + case FinalEP(e, p) ⇒ Result(e, p) + case _ ⇒ + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(entity._1, defSite) + InterimResult( + entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + List(), + c + ) + }) + case _ ⇒ + val e: Integer = defSite + state.appendToFpe2Sci( + e, StringConstancyProperty.lb.stringConstancyInformation + ) + results.append(Result(e, StringConstancyProperty.lb)) + } + } + } + results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + } else { + Result(instr, StringConstancyProperty.lb) + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 5ec51fbece..17f6bbdf2d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -7,6 +7,7 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.ai.ImmediateVMExceptionsOriginOffset @@ -54,11 +55,12 @@ import org.opalj.tac.TACode * @author Patrick Mell */ class InterproceduralInterpretationHandler( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], - ps: PropertyStore, - declaredMethods: DeclaredMethods, - state: InterproceduralComputationState, - c: ProperOnUpdateContinuation + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + ps: PropertyStore, + declaredMethods: DeclaredMethods, + fieldAccessInformation: FieldAccessInformation, + state: InterproceduralComputationState, + c: ProperOnUpdateContinuation ) extends InterpretationHandler(tac) { /** @@ -151,7 +153,13 @@ class InterproceduralInterpretationHandler( } r case Assignment(_, _, expr: GetField[V]) ⇒ - new InterproceduralFieldInterpreter(cfg, this, callees).interpret(expr, defSite) + val r = new InterproceduralFieldInterpreter( + state, this, ps, fieldAccessInformation, c + ).interpret(expr, defSite) + if (!r.isInstanceOf[Result]) { + processedDefSites.remove(defSite) + } + r case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ val r = new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c @@ -230,13 +238,14 @@ object InterproceduralInterpretationHandler { * @see [[org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural.IntraproceduralInterpretationHandler]] */ def apply( - tac: TACode[TACMethodParameter, DUVar[ValueInformation]], - ps: PropertyStore, - declaredMethods: DeclaredMethods, - state: InterproceduralComputationState, - c: ProperOnUpdateContinuation + tac: TACode[TACMethodParameter, DUVar[ValueInformation]], + ps: PropertyStore, + declaredMethods: DeclaredMethods, + fieldAccessInformation: FieldAccessInformation, + state: InterproceduralComputationState, + c: ProperOnUpdateContinuation ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - tac, ps, declaredMethods, state, c + tac, ps, declaredMethods, fieldAccessInformation, state, c ) } \ No newline at end of file From 50de36889c4e31971ed7a43a703ea08d71f5f2b2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 14:58:31 +0100 Subject: [PATCH 229/316] Refined the handling of fields. --- .../InterproceduralFieldInterpreter.scala | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 4513a74269..79bb71514a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -11,6 +11,8 @@ import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.tac.GetField import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter @@ -48,7 +50,8 @@ class InterproceduralFieldInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = + override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + val defSitEntity: Integer = defSite if (InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { val results = ListBuffer[ProperPropertyComputationResult]() fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { @@ -72,17 +75,30 @@ class InterproceduralFieldInterpreter( ) }) case _ ⇒ - val e: Integer = defSite state.appendToFpe2Sci( - e, StringConstancyProperty.lb.stringConstancyInformation + defSitEntity, StringConstancyProperty.lb.stringConstancyInformation ) - results.append(Result(e, StringConstancyProperty.lb)) + results.append(Result(defSitEntity, StringConstancyProperty.lb)) } } } - results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + if (results.isEmpty) { + // No methods, which write the field, were found => Field could either be null or + // any value + val possibleStrings = "(^null$|"+StringConstancyInformation.UnknownWordSymbol+")" + val sci = StringConstancyInformation( + StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings + ) + Result(defSitEntity, StringConstancyProperty(sci)) + } else { + // If available, return an intermediate result to indicate that the computation + // needs to be continued + results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + } } else { + // Unknown type => Cannot further approximate Result(instr, StringConstancyProperty.lb) } + } } From 365a7010b03a1a70b5e651a4e5ab5445bc106fb3 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 14:58:49 +0100 Subject: [PATCH 230/316] Added some important details to a test case. --- .../fixtures/string_analysis/InterproceduralTestMethods.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 0c05ec5969..27f95aee63 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -414,7 +414,8 @@ public String tieName(String var0, Remote remote) { } @StringDefinitionsCollection( - value = "a case where a string field is read", + value = "a case where a string field is read (the expected strings should actually" + + "contain the value written in belongsToFieldReadTest!)", stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, From 01e45322a128bcad7318bad4254ffa0edaaae202 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 15:01:47 +0100 Subject: [PATCH 231/316] Added a test case where a field is read that is not written at all. --- .../InterproceduralTestMethods.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 27f95aee63..eddd082232 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -30,6 +30,8 @@ public class InterproceduralTestMethods { private String myField; + private String noWriteField; + /** * {@see LocalTestMethods#analyzeString} */ @@ -431,6 +433,18 @@ private void belongsToFieldReadTest() { myField = "another value"; } + @StringDefinitionsCollection( + value = "a case where a field is read which is not written", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "(^null$|\\w)" + ) + }) + public void fieldWithNoWriteTest() { + analyzeString(noWriteField); + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { From 7ceabbb038dd232404048317eeecfa73d76fe323 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 16:40:06 +0100 Subject: [PATCH 232/316] Fixed a bug in the natural loop finding procedure. Sometimes, not entire loops were captured but only parts. --- OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index d2ac05c47e..4dec3579f1 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -787,9 +787,11 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( // removed as it is still implicitly contained in the loop denoted by x to y2 // (note that this does not apply for nested loops, they are kept) backedges.filter { - case (_: Int, oldFrom: Int) ⇒ oldFrom == destIndex + case (oldFrom: Int, oldTo: Int) ⇒ oldTo == destIndex && oldFrom < from }.foreach(backedges -= _) - backedges.append((from, destIndex)) + if (backedges.isEmpty) { + backedges.append((from, destIndex)) + } } seenNodes.append(from) From 8136bf41079aba41b6900eb56a135a3e9e0dc4bc Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 20:26:04 +0100 Subject: [PATCH 233/316] The "fix" done with commit #729bee2 was not entirely correct (but now!). --- OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 4dec3579f1..cc2b55061a 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -786,10 +786,12 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( // before y2; this method only saves one loop per loop header, thus y1 is // removed as it is still implicitly contained in the loop denoted by x to y2 // (note that this does not apply for nested loops, they are kept) + val hasDest = backedges.exists(_._2 == destIndex) + var removedBackedge = false backedges.filter { - case (oldFrom: Int, oldTo: Int) ⇒ oldTo == destIndex && oldFrom < from - }.foreach(backedges -= _) - if (backedges.isEmpty) { + case (oldTo: Int, oldFrom: Int) ⇒ oldFrom == destIndex && oldTo < from + }.foreach { toRemove ⇒ removedBackedge = true; backedges -= toRemove } + if (!hasDest || removedBackedge) { backedges.append((from, destIndex)) } } From bb9af972c35ae9149545855a911b1d0e680ddbd6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 20:26:43 +0100 Subject: [PATCH 234/316] Formatted the file. --- .../IntraproceduralStringAnalysis.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index 9427b818bf..e4ee23bf31 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -73,14 +73,14 @@ class IntraproceduralStringAnalysis( * have all required information ready for a final result. */ private case class ComputationState( - // The lean path that was computed - computedLeanPath: Path, - // A mapping from DUVar elements to the corresponding indices of the FlatPathElements - var2IndexMapping: mutable.Map[V, Int], - // A mapping from values of FlatPathElements to StringConstancyInformation - fpe2sci: mutable.Map[Int, StringConstancyInformation], - // The three-address code of the method in which the entity under analysis resides - tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + // The lean path that was computed + computedLeanPath: Path, + // A mapping from DUVar elements to the corresponding indices of the FlatPathElements + var2IndexMapping: mutable.Map[V, Int], + // A mapping from values of FlatPathElements to StringConstancyInformation + fpe2sci: mutable.Map[Int, StringConstancyInformation], + // The three-address code of the method in which the entity under analysis resides + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] ) def analyze(data: P): ProperPropertyComputationResult = { @@ -348,7 +348,7 @@ sealed trait IntraproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule * Executor for the lazy analysis. */ object LazyIntraproceduralStringAnalysis - extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + extends IntraproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData From 090a311d4599b8489f89a1b9f53e2b29eafeb76e Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 20:35:22 +0100 Subject: [PATCH 235/316] Refined the getStartAndEndIndexOfCondWithAlternative procedure. --- .../preprocessing/AbstractPathFinder.scala | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 1d0dc065fb..7ba15029b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -45,7 +45,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''e1''. */ protected case class HierarchicalCSOrder( - hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] ) /** @@ -93,7 +93,14 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { case goto: Goto ⇒ // Find the goto that points after the "else" part (the assumption is that // this goto is the very last element of the current branch - endSite = goto.targetStmt - 1 + endSite = goto.targetStmt + // The goto might point back at the beginning of a loop; if so, the end of + // the if/else is denoted by the end of the loop + if (endSite < branchingSite) { + endSite = cfg.findNaturalLoops().filter(_.head == endSite).head.last + } else { + endSite -= 1 + } case _ ⇒ // No goto available => Jump after next block var nextIf: Option[If[V]] = None @@ -670,8 +677,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -756,7 +763,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_)= Unit) + alreadySeen.foreach(seenNodes(_) = Unit) seenNodes(from) = Unit while (stack.nonEmpty) { From cdd76a46d3ef61a9594383da117356fb22b39238 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 6 Mar 2019 22:19:39 +0100 Subject: [PATCH 236/316] Added a condition to detect conditionals without alternative. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 7ba15029b1..cf9d482fab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -711,6 +711,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val branches = successors.reverse.tail.reverse val lastEle = successors.last + // If an "if" ends at the end of a loop, it cannot have an else + if (cfg.findNaturalLoops().exists(_.last == lastEle - 1)) { + return true + } + val indexIf = cfg.bb(lastEle) match { case bb: BasicBlock ⇒ val ifPos = bb.startPC.to(bb.endPC).filter( From 2917ae6ecaa0a98523db5a375fc91c9be316de1d Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 07:32:06 +0100 Subject: [PATCH 237/316] Refined the handling of switch. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index cf9d482fab..ceeafec866 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -868,8 +868,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { protected def processSwitch(stmt: Int): CSInfo = { val switch = cfg.code.instructions(stmt).asSwitch val caseStmts = switch.caseStmts.sorted - // TODO: How about only one case? - val end = cfg.code.instructions(caseStmts.tail.head - 1).asGoto.targetStmt - 1 + // From the last to the first one, find the first case that points after the switch + val caseGoto = caseStmts.reverse.find { caseIndex ⇒ + cfg.code.instructions(caseIndex - 1).isInstanceOf[Goto] + }.get - 1 + val end = cfg.code.instructions(caseGoto).asGoto.targetStmt - 1 val containsDefault = caseStmts.length == caseStmts.distinct.length val pathType = if (containsDefault) NestedPathType.CondWithAlternative else From 20d8f29fec16afee89601311f571f321b5317323 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 08:00:20 +0100 Subject: [PATCH 238/316] Refined the handling of int/char values. --- ...alFunctionCallPreparationInterpreter.scala | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 2d0b827a6f..d27adc78ab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -303,20 +303,25 @@ class VirtualFunctionCallPreparationInterpreter( return values.find(!_.isInstanceOf[Result]).get } - var valueSci = StringConstancyInformation.reduceMultiple(values.map { + val sciValues = values.map { StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation - }) + } + val defSitesValueSci = StringConstancyInformation.reduceMultiple(sciValues) // If defSiteHead points to a "New", value will be the empty list. In that case, process // the first use site (which is the call) - if (valueSci.isTheNeutralElement) { + var newValueSci = StringConstancyInformation.getNeutralElement + if (defSitesValueSci.isTheNeutralElement) { val ds = cfg.code.instructions(defSites.head).asAssignment.targetVar.usedBy.toArray.min val r = exprHandler.processDefSite(ds, params) // Again, defer the computation if there is no final result (yet) if (!r.isInstanceOf[Result]) { + newValueSci = defSitesValueSci return r } else { - valueSci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + newValueSci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation } + } else { + newValueSci = defSitesValueSci } val finalSci = param.value.computationalType match { @@ -325,22 +330,27 @@ class VirtualFunctionCallPreparationInterpreter( // The value was already computed above; however, we need to check whether the // append takes an int value or a char (if it is a constant char, convert it) if (call.descriptor.parameterType(0).isCharType && - valueSci.constancyLevel == StringConstancyLevel.CONSTANT) { - valueSci.copy( - possibleStrings = valueSci.possibleStrings.toInt.toChar.toString - ) + defSitesValueSci.constancyLevel == StringConstancyLevel.CONSTANT) { + if (defSitesValueSci.isTheNeutralElement) { + StringConstancyProperty.lb.stringConstancyInformation + } else { + val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci ⇒ + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + } + StringConstancyInformation.reduceMultiple(charSciValues) + } } else { - valueSci + newValueSci } case ComputationalTypeFloat ⇒ InterpretationHandler.getConstancyInfoForDynamicFloat // Otherwise, try to compute case _ ⇒ - valueSci + newValueSci } val e: Integer = defSites.head - state.appendToFpe2Sci(e, valueSci, reset = true) + state.appendToFpe2Sci(e, newValueSci, reset = true) Result(e, StringConstancyProperty(finalSci)) } From 9816b6eb6eba02aeba3457552547b9d28da46ee7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 09:20:18 +0100 Subject: [PATCH 239/316] Refined the handling of switch. --- .../preprocessing/AbstractPathFinder.scala | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index ceeafec866..ebf4e07aeb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -768,7 +768,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_) = Unit) + alreadySeen.foreach(seenNodes(_)= Unit) seenNodes(from) = Unit while (stack.nonEmpty) { @@ -869,10 +869,23 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val switch = cfg.code.instructions(stmt).asSwitch val caseStmts = switch.caseStmts.sorted // From the last to the first one, find the first case that points after the switch - val caseGoto = caseStmts.reverse.find { caseIndex ⇒ + val caseGotoOption = caseStmts.reverse.find { caseIndex ⇒ cfg.code.instructions(caseIndex - 1).isInstanceOf[Goto] - }.get - 1 - val end = cfg.code.instructions(caseGoto).asGoto.targetStmt - 1 + } + // If no such case is present, find the next goto after the default case + val posGoTo = if (caseGotoOption.isEmpty) { + var i = switch.defaultStmt + while (!cfg.code.instructions(i).isInstanceOf[Goto]) { + i += 1 + } + i + } else caseGotoOption.get - 1 + var end = cfg.code.instructions(posGoTo).asGoto.targetStmt - 1 + // In case the goto points at the a loop, do not set the start index of the loop as end + // position but the index of the goto + if (end < stmt) { + end = posGoTo + } val containsDefault = caseStmts.length == caseStmts.distinct.length val pathType = if (containsDefault) NestedPathType.CondWithAlternative else From 42e9aeb802e52f74600e2b976b8790bf9b53b154 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 7 Mar 2019 12:19:36 +0100 Subject: [PATCH 240/316] Retrieve the TAC via the TAC provider again (StringAnalysisTest will be changed to use the TAC provider as well for extracting relevant UVars => for the definition sites to match, the TAC provider is required here). --- .../IntraproceduralStringAnalysis.scala | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index e4ee23bf31..b67c581b2e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -35,6 +35,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.DUVar import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode @@ -86,16 +87,21 @@ class IntraproceduralStringAnalysis( def analyze(data: P): ProperPropertyComputationResult = { // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val tacaiEOptP = ps(data._2, TACAI.key) - var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = null - if (tacaiEOptP.hasUBP) { - if (tacaiEOptP.ub.tac.isEmpty) { - // No TAC available, e.g., because the method has no body - return Result(data, StringConstancyProperty.lb) - } else { - tac = tacaiEOptP.ub.tac.get - } - } + + val tacProvider = p.get(DefaultTACAIKey) + val tac = tacProvider(data._2) + + // Uncomment the following code to get the TAC from the property store + // val tacaiEOptP = ps(data._2, TACAI.key) + // var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = null + // if (tacaiEOptP.hasUBP) { + // if (tacaiEOptP.ub.tac.isEmpty) { + // // No TAC available, e.g., because the method has no body + // return Result(data, StringConstancyProperty.lb) + // } else { + // tac = tacaiEOptP.ub.tac.get + // } + // } val cfg = tac.cfg val stmts = tac.stmts From 8745dea4a5498f3c55bd8d699f3894a708faea35 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 10:36:42 +0100 Subject: [PATCH 241/316] Had to change how the property store is setup in InterproceduralStringAnalysisTest. For the reson, see the class comment. --- .../org/opalj/fpcf/StringAnalysisTest.scala | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 19da829a1f..f099fd6b3a 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -8,7 +8,9 @@ import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.log.LogContext import org.opalj.collection.immutable.ConstArray +import org.opalj.fpcf.seq.PKESequentialPropertyStore import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method @@ -20,8 +22,9 @@ import org.opalj.br.fpcf.cg.properties.ReflectionRelatedCallees import org.opalj.br.fpcf.cg.properties.SerializationRelatedCallees import org.opalj.br.fpcf.cg.properties.StandardInvokeCallees import org.opalj.br.fpcf.cg.properties.ThreadRelatedIncompleteCallSites +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis -import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall @@ -29,9 +32,7 @@ import org.opalj.tac.fpcf.analyses.cg.reflection.TriggeredReflectionRelatedCalls import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis @@ -41,6 +42,8 @@ import org.opalj.tac.fpcf.analyses.TACAITransformer import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis +import org.opalj.tac.DefaultTACAIKey +import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -196,9 +199,14 @@ object IntraproceduralStringAnalysisTest { } /** - * Tests whether the [[InterproceduralStringAnalysis]] works correctly with respect to some + * Tests whether the InterproceduralStringAnalysis works correctly with respect to some * well-defined tests. * + * @note We could use a manager to run the analyses, however, doing so leads to the fact that the + * property store does not compute all properties, especially TAC. The reason being is that + * a ''shutdown'' call prevents further computations. Thus, we do all this manually here. + * (Detected and fixed in a session with Dominik.) + * * @author Patrick Mell */ class InterproceduralStringAnalysisTest extends PropertiesTest { @@ -210,10 +218,15 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { InterproceduralStringAnalysisTest.filesToLoad ) val p = Project(runner.getRelevantProjectFiles, Array[File]()) - - val manager = p.get(FPCFAnalysesManagerKey) - val (ps, analyses) = manager.runAll( - TACAITransformer, + p.getOrCreateProjectInformationKeyInitializationData( + PropertyStoreKey, + (context:List[PropertyStoreContext[AnyRef]]) ⇒ { + implicit val lg:LogContext = p.logContext + PropertyStore.updateTraceFallbacks(true) + PKESequentialPropertyStore.apply(context:_*) + }) + + val analyses: Set[ComputationSpecification[FPCFAnalysis]] = Set(TACAITransformer, LazyL0BaseAIAnalysis, RTACallGraphAnalysisScheduler, TriggeredStaticInitializerAnalysis, @@ -232,16 +245,23 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { )), LazyInterproceduralStringAnalysis ) + val ps = p.get(PropertyStoreKey) + ps.setupPhase(analyses.flatMap(_.derives.map(_.pk))) + var initData: Map[ComputationSpecification[_], Any] = Map() + analyses.foreach{a ⇒ + initData += a → a.init(ps) + a.beforeSchedule(ps) + } + var scheduledAnalyses: List[FPCFAnalysis] = List() + analyses.foreach{a ⇒ + scheduledAnalyses ::=a.schedule(ps, initData(a).asInstanceOf[a.InitializationData]) + } - val testContext = TestContext( - p, ps, List(new InterproceduralStringAnalysis(p)) ++ analyses.map(_._2) - ) - LazyInterproceduralStringAnalysis.init(p, ps) - LazyInterproceduralStringAnalysis.schedule(ps, null) + val testContext = TestContext(p, ps, scheduledAnalyses) val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) - testContext.propertyStore.shutdown() validateProperties(testContext, eas, Set("StringConstancy")) + testContext.propertyStore.shutdown() ps.waitOnPhaseCompletion() } From c8a22c0e7623a9fc9547f7fbc1fe07b37b379041 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 10:44:13 +0100 Subject: [PATCH 242/316] Slightly simplified the ArrayPreparationInterpreter. --- .../string_analysis/InterproceduralTestMethods.java | 6 +++--- .../interprocedural/ArrayPreparationInterpreter.scala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index eddd082232..12ef199b23 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -94,7 +94,7 @@ public void fromStaticMethodWithParamTest() { @StringDefinitionsCollection( value = "a case where a static method is called that returns a string but are not " - + "within this project => cannot / will not interpret", + + "within this project => cannot / will not be interpret", stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, @@ -131,8 +131,8 @@ public void methodOutOfScopeTest() throws FileNotFoundException { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|java.lang.Runtime|" - + "java.lang.(Integer|Object|Runtime)|\\w)" + expectedStrings = "(java.lang.Object|\\w|java.lang.(Integer|" + + "Object|Runtime)|\\w)" ) }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 55ebc9ec4d..ddb714d843 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -59,7 +59,7 @@ class ArrayPreparationInterpreter( allDefSites.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { case (ds, r: Result) ⇒ - state.appendResultToFpe2Sci(ds, r, reset = true) + state.appendResultToFpe2Sci(ds, r) results.append(r) case (_, ir: ProperPropertyComputationResult) ⇒ results.append(ir) } From c508229fa2bd3db6711ab91b2ea4f24e78f158fe Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 11:13:14 +0100 Subject: [PATCH 243/316] Formatted the file. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index ebf4e07aeb..030e711f7f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -45,7 +45,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''e1''. */ protected case class HierarchicalCSOrder( - hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] ) /** @@ -677,8 +677,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no From 069ab761182b4be842b7a7a2cfac7b3d98aca087 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 11:15:32 +0100 Subject: [PATCH 244/316] Refined the handling of the TAC. --- .../InterproceduralComputationState.scala | 49 ++- .../InterproceduralStringAnalysis.scala | 305 +++++++----------- .../AbstractStringInterpreter.scala | 15 +- .../InterpretationHandler.scala | 3 + ...InterproceduralInterpretationHandler.scala | 76 +++-- ...ralNonVirtualFunctionCallInterpreter.scala | 5 +- ...ceduralStaticFunctionCallInterpreter.scala | 42 ++- ...alFunctionCallPreparationInterpreter.scala | 15 +- .../VirtualFunctionCallFinalizer.scala | 5 + 9 files changed, 287 insertions(+), 228 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 8d731157cd..cb637c818d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -13,12 +13,14 @@ import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.Method import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.DUVar import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.InterproceduralInterpretationHandler import org.opalj.tac.FunctionCall +import org.opalj.tac.VirtualFunctionCall /** * This class is to be used to store state information that are required at a later point in @@ -117,6 +119,18 @@ case class InterproceduralComputationState(entity: P) { */ val entity2Function: mutable.Map[P, ListBuffer[FunctionCall[V]]] = mutable.Map() + /** + * A mapping from a method to definition sites which indicates that a method is still prepared, + * e.g., the TAC is still to be retrieved, and the list values indicate the defintion sites + * which depend on the preparations. + */ + val methodPrep2defSite: mutable.Map[Method, ListBuffer[Int]] = mutable.Map() + + /** + * A mapping which indicates whether a virtual function call is fully prepared. + */ + val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[V], Boolean] = mutable.Map() + /** * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, * however, only if `defSite` is not yet present. @@ -131,7 +145,8 @@ case class InterproceduralComputationState(entity: P) { /** * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] - * map accordingly, however, only if `defSite` is not yet present. + * map accordingly, however, only if `defSite` is not yet present and `sci` not present within + * the list of `defSite`. */ def appendToFpe2Sci( defSite: Int, sci: StringConstancyInformation, reset: Boolean = false @@ -139,7 +154,9 @@ case class InterproceduralComputationState(entity: P) { if (reset || !fpe2sci.contains(defSite)) { fpe2sci(defSite) = ListBuffer() } - fpe2sci(defSite).append(sci) + if (!fpe2sci(defSite).contains(sci)) { + fpe2sci(defSite).append(sci) + } } /** @@ -152,4 +169,32 @@ case class InterproceduralComputationState(entity: P) { var2IndexMapping(entity).append(defSite) } + /** + * Takes a TAC EPS as well as a definition site and append it to [[methodPrep2defSite]]. + */ + def appendToMethodPrep2defSite(m: Method, defSite: Int): Unit = { + if (!methodPrep2defSite.contains(m)) { + methodPrep2defSite(m) = ListBuffer() + } + if (!methodPrep2defSite(m).contains(defSite)) { + methodPrep2defSite(m).append(defSite) + } + } + + /** + * Removed the given definition site for the given method from [[methodPrep2defSite]]. If the + * entry for `m` in `methodPrep2defSite` is empty, the entry for `m` is removed. + */ + def removeFromMethodPrep2defSite(m: Method, defSite: Int): Unit = { + if (methodPrep2defSite.contains(m)) { + val index = methodPrep2defSite(m).indexOf(defSite) + if (index > -1) { + methodPrep2defSite(m).remove(index) + } + if (methodPrep2defSite(m).isEmpty) { + methodPrep2defSite.remove(m) + } + } + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 770e81295f..2e4b5d9bd0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -6,7 +6,6 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Property @@ -25,7 +24,6 @@ import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.FieldType -import org.opalj.br.Method import org.opalj.br.analyses.FieldAccessInformationKey import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder @@ -43,7 +41,6 @@ import org.opalj.tac.Assignment import org.opalj.tac.DUVar import org.opalj.tac.FunctionCall import org.opalj.tac.MethodCall -import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType @@ -84,9 +81,21 @@ class InterproceduralStringAnalysis( private val declaredMethods = project.get(DeclaredMethodsKey) private final val fieldAccessInformation = project.get(FieldAccessInformationKey) + /** + * Returns the current interim result for the given state. + */ + private def getInterimResult( + state: InterproceduralComputationState + ): InterimResult[StringConstancyProperty] = InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + continuation(state) + ) + def analyze(data: P): ProperPropertyComputationResult = { val state = InterproceduralComputationState(data) - // TODO: Make the entity of the analysis take a declared method instead of a method val dm = declaredMethods(data._2) val tacaiEOptP = ps(data._2, TACAI.key) @@ -99,13 +108,6 @@ class InterproceduralStringAnalysis( } } else { state.dependees = tacaiEOptP :: state.dependees - InterimResult( - data, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) } val calleesEOptP = ps(dm, Callees.key) @@ -114,13 +116,7 @@ class InterproceduralStringAnalysis( determinePossibleStrings(state) } else { state.dependees = calleesEOptP :: state.dependees - InterimResult( - data, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) + getInterimResult(state) } } @@ -136,7 +132,10 @@ class InterproceduralStringAnalysis( val defSites = uvar.definedBy.toArray.sorted val stmts = state.tac.stmts - val tacProvider = p.get(SimpleTACAIKey) + if (state.tac == null || state.callees == null) { + return getInterimResult(state) + } + if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( state.tac, ps, declaredMethods, fieldAccessInformation, state, continuation(state) @@ -188,35 +187,17 @@ class InterproceduralStringAnalysis( val callersEOptP = ps(dm, CallersProperty.key) if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub - if (!registerParams(state, tacProvider)) { - return InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) + if (!registerParams(state)) { + return getInterimResult(state) } } else { state.dependees = callersEOptP :: state.dependees - return InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) + return getInterimResult(state) } } if (state.parameterDependeesCount > 0) { - return InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) + return getInterimResult(state) } else { state.isSetupCompleted = true } @@ -244,7 +225,7 @@ class InterproceduralStringAnalysis( } } } else { - if (computeResultsForPath(state.computedLeanPath, state)) { + if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci ).reduce(true) @@ -252,10 +233,12 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - if (computeResultsForPath(state.computedLeanPath, state)) { + if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci ).reduce(true) + } else { + return getInterimResult(state) } // No need to cover the else branch: interimResults.nonEmpty => dependees were added to // state.dependees, i.e., the if that checks whether state.dependees is non-empty will @@ -263,13 +246,7 @@ class InterproceduralStringAnalysis( } if (state.dependees.nonEmpty) { - InterimResult( - state.entity, - StringConstancyProperty.ub, - StringConstancyProperty.lb, - state.dependees, - continuation(state) - ) + getInterimResult(state) } else { InterproceduralStringAnalysis.unregisterParams(state.entity) Result(state.entity, StringConstancyProperty(sci)) @@ -287,136 +264,103 @@ class InterproceduralStringAnalysis( */ private def continuation( state: InterproceduralComputationState - )(eps: SomeEPS): ProperPropertyComputationResult = { - val inputData = state.entity - eps.pk match { - case TACAI.key ⇒ eps match { - case FinalP(tac: TACAI) ⇒ - // Set the TAC only once (the TAC might be requested for other methods, so this - // makes sure we do not overwrite the state's TAC) - if (state.tac == null) { - state.tac = tac.tac.get + )(eps: SomeEPS): ProperPropertyComputationResult = eps.pk match { + case TACAI.key ⇒ eps match { + case FinalP(tac: TACAI) ⇒ + // Set the TAC only once (the TAC might be requested for other methods, so this + // makes sure we do not overwrite the state's TAC) + if (state.tac == null) { + state.tac = tac.tac.get + } + state.dependees = state.dependees.filter(_.e != eps.e) + determinePossibleStrings(state) + case _ ⇒ + state.dependees = state.dependees.filter(_.e != eps.e) + state.dependees = eps :: state.dependees + getInterimResult(state) + } + case Callees.key ⇒ eps match { + case FinalP(callees: Callees) ⇒ + state.callees = callees + state.dependees = state.dependees.filter(_.e != eps.e) + if (state.dependees.isEmpty) { + determinePossibleStrings(state) + } else { + getInterimResult(state) + } + case _ ⇒ + state.dependees = state.dependees.filter(_.e != eps.e) + state.dependees = eps :: state.dependees + getInterimResult(state) + } + case CallersProperty.key ⇒ eps match { + case FinalP(callers: CallersProperty) ⇒ + state.callers = callers + state.dependees = state.dependees.filter(_.e != eps.e) + if (state.dependees.isEmpty) { + registerParams(state) + determinePossibleStrings(state) + } else { + getInterimResult(state) + } + case _ ⇒ + state.dependees = state.dependees.filter(_.e != eps.e) + state.dependees = eps :: state.dependees + getInterimResult(state) + } + case StringConstancyProperty.key ⇒ + eps match { + case FinalP(p: StringConstancyProperty) ⇒ + val resultEntity = eps.e.asInstanceOf[P] + // If necessary, update the parameter information with which the + // surrounding function / method of the entity was called with + if (state.paramResultPositions.contains(resultEntity)) { + val pos = state.paramResultPositions(resultEntity) + state.params(pos._1)(pos._2) = p.stringConstancyInformation + state.paramResultPositions.remove(resultEntity) + state.parameterDependeesCount -= 1 + state.dependees = state.dependees.filter(_.e != eps.e) } - state.dependees = state.dependees.filter(_.e != eps.e) - if (state.dependees.isEmpty) { - determinePossibleStrings(state) - } else { - InterimResult( - inputData, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) + + // If necessary, update parameter information of function calls + if (state.entity2Function.contains(resultEntity)) { + state.var2IndexMapping(resultEntity._1).foreach(state.appendToFpe2Sci( + _, p.stringConstancyInformation + )) + // Update the state + state.entity2Function(resultEntity).foreach { f ⇒ + val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) + val result = Result(resultEntity, p) + state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result + // TODO: Is that correct? (rather remove only the function from the list) + state.entity2Function.remove(resultEntity) + } + // Continue only after all necessary function parameters are evaluated + if (state.entity2Function.nonEmpty) { + return getInterimResult(state) + } else { + // We could try to determine a final result before all function + // parameter information are available, however, this will + // definitely result in finding some intermediate result. Thus, + // defer this computations when we know that all necessary + // information are available + state.entity2Function.clear() + if (!computeResultsForPath(state.computedLeanPath, state)) { + return determinePossibleStrings(state) + } + } } - case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees, continuation(state) - ) - case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") - } - case Callees.key ⇒ eps match { - case FinalP(callees: Callees) ⇒ - state.callees = callees - state.dependees = state.dependees.filter(_.e != eps.e) - if (state.dependees.isEmpty) { - determinePossibleStrings(state) + + if (state.isSetupCompleted && state.parameterDependeesCount == 0) { + processFinalP(state, eps.e, p) } else { - InterimResult( - inputData, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) - } - case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees, continuation(state) - ) - case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") - } - case CallersProperty.key ⇒ eps match { - case FinalP(callers: CallersProperty) ⇒ - state.callers = callers - state.dependees = state.dependees.filter(_.e != eps.e) - if (state.dependees.isEmpty) { - registerParams(state, p.get(SimpleTACAIKey)) determinePossibleStrings(state) - } else { - InterimResult( - inputData, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) } - case InterimLUBP(lb, ub) ⇒ InterimResult( - inputData, lb, ub, state.dependees, continuation(state) - ) - case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") + case _ ⇒ + state.dependees = state.dependees.filter(_.e != eps.e) + state.dependees = eps :: state.dependees + getInterimResult(state) } - case StringConstancyProperty.key ⇒ - eps match { - case FinalP(p: StringConstancyProperty) ⇒ - val resultEntity = eps.e.asInstanceOf[P] - // If necessary, update the parameter information with which the - // surrounding function / method of the entity was called with - if (state.paramResultPositions.contains(resultEntity)) { - val pos = state.paramResultPositions(resultEntity) - state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(resultEntity) - state.parameterDependeesCount -= 1 - state.dependees = state.dependees.filter(_.e != eps.e) - } - - // If necessary, update parameter information of function calls - if (state.entity2Function.contains(resultEntity)) { - state.var2IndexMapping(resultEntity._1).foreach(state.appendToFpe2Sci( - _, p.stringConstancyInformation - )) - // Update the state - state.entity2Function(resultEntity).foreach { f ⇒ - val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) - val result = Result(resultEntity, p) - state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result - state.entity2Function.remove(resultEntity) // TODO: Is that correct? (rather remove only the function from the list) - } - // Continue only after all necessary function parameters are evaluated - if (state.entity2Function.nonEmpty) { - return InterimResult( - inputData, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) - ) - } else { - // We could try to determine a final result before all function - // parameter information are available, however, this will - // definitely result in finding some intermediate result. Thus, - // defer this computations when we know that all necessary - // information are available - state.entity2Function.clear() - if (!computeResultsForPath(state.computedLeanPath, state)) { - return determinePossibleStrings(state) - } - } - } - - if (state.isSetupCompleted && state.parameterDependeesCount == 0) { - processFinalP(state, eps.e, p) - } else { - determinePossibleStrings(state) - } - case InterimLUBP(lb, ub) ⇒ - state.dependees = state.dependees.filter(_.e != eps.e) - state.dependees = eps :: state.dependees - InterimResult( - inputData, lb, ub, state.dependees, continuation(state) - ) - case _ ⇒ throw new IllegalStateException("Neither FinalP nor InterimResult") - } - } } private def finalizePreparations( @@ -470,18 +414,12 @@ class InterproceduralStringAnalysis( state.appendToFpe2Sci(_, currentSci) } - // No more dependees => Return the result for this analysis run state.dependees = state.dependees.filter(_.e != e) + // No more dependees => Return the result for this analysis run if (state.dependees.isEmpty) { computeFinalResult(state) } else { - InterimResult( - state.entity, - StringConstancyProperty.ub, - StringConstancyProperty.lb, - state.dependees, - continuation(state) - ) + getInterimResult(state) } } @@ -491,8 +429,7 @@ class InterproceduralStringAnalysis( * interpretations are registered using [[InterproceduralStringAnalysis.registerParams]]. */ private def registerParams( - state: InterproceduralComputationState, - tacProvider: Method ⇒ TACode[TACMethodParameter, DUVar[ValueInformation]] + state: InterproceduralComputationState ): Boolean = { var hasIntermediateResult = false state.callers.callers(declaredMethods).toSeq.zipWithIndex.foreach { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 0f8813c0ff..4cd3d7e5af 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -4,6 +4,7 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore @@ -53,25 +54,27 @@ abstract class AbstractStringInterpreter( type T <: Any /** - * Either returns the TAC for the given method or otherwise registers dependees. + * Returns the EPS retrieved from querying the given property store for the given method as well + * as the TAC, if it could already be determined. If not, thus function registers a dependee + * within the given state. * * @param ps The property store to use. * @param m The method to get the TAC for. * @param s The computation state whose dependees might be extended in case the TAC is not * immediately ready. - * @return Returns `Some(tac)` if the TAC is already available or `None` otherwise. + * @return Returns (eps, tac). */ protected def getTACAI( ps: PropertyStore, m: Method, s: InterproceduralComputationState - ): Option[TACode[TACMethodParameter, V]] = { + ): (EOptionP[Method, TACAI], Option[TACode[TACMethodParameter, V]]) = { val tacai = ps(m, TACAI.key) if (tacai.hasUBP) { - tacai.ub.tac + (tacai, tacai.ub.tac) } else { s.dependees = tacai :: s.dependees - None + (tacai, None) } } @@ -141,7 +144,7 @@ abstract class AbstractStringInterpreter( case (nextParam, middleIndex) ⇒ nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { case (ds, innerIndex) ⇒ - val r = iHandler.processDefSite(ds, List()) + val r = iHandler.processDefSite(ds) if (!r.isInstanceOf[Result]) { val interim = r.asInstanceOf[InterimResult[StringConstancyProperty]] if (!functionArgsPos.contains(funCall)) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index b91c332527..235e3704de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -18,6 +18,7 @@ import org.opalj.tac.Stmt import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.DUVar +import org.opalj.tac.GetField import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.TACStmts @@ -153,6 +154,8 @@ object InterpretationHandler { defSites.append(next) case vfc: VirtualFunctionCall[V] ⇒ stack.pushAll(vfc.receiver.asVar.definedBy.filter(_ >= 0).toArray) + case _: GetField[V] ⇒ + defSites.append(next) case _ ⇒ // E.g., NullExpr } case _ ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 17f6bbdf2d..7f697a7cdf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -111,7 +111,13 @@ class InterproceduralInterpretationHandler( processedDefSites.remove(defSite) result case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - new ArrayPreparationInterpreter(cfg, this, state, params).interpret(expr, defSite) + val r = new ArrayPreparationInterpreter( + cfg, this, state, params + ).interpret(expr, defSite) + if (!r.isInstanceOf[Result]) { + processedDefSites.remove(defSite) + } + r case Assignment(_, _, expr: New) ⇒ val result = new NewInterpreter(cfg, this).interpret(expr, defSite) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) @@ -120,23 +126,13 @@ class InterproceduralInterpretationHandler( new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) case ExprStmt(_, expr: GetStatic) ⇒ new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) - case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - val r = new VirtualFunctionCallPreparationInterpreter( - cfg, this, ps, state, declaredMethods, params, c - ).interpret(expr, defSite) - // In case no final result could be computed, remove this def site from the list of - // processed def sites to make sure that is can be compute again (when all final - // results are available); we use nonFinalFunctionArgs because if it does not - // contain expr, it can be finalized later on without processing the function again - if (state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(defSite) - } - r + case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) + case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (state.nonFinalFunctionArgs.contains(expr)) { + if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } r @@ -148,7 +144,7 @@ class InterproceduralInterpretationHandler( val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (state.nonFinalFunctionArgs.contains(expr)) { + if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } r @@ -160,19 +156,11 @@ class InterproceduralInterpretationHandler( processedDefSites.remove(defSite) } r - case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ - val r = new VirtualFunctionCallPreparationInterpreter( - cfg, this, ps, state, declaredMethods, params, c - ).interpret(expr, defSite) - if (state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(defSite) - } - r case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (state.nonFinalFunctionArgs.contains(expr)) { + if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } r @@ -186,13 +174,51 @@ class InterproceduralInterpretationHandler( ).interpret(nvmc, defSite) result match { case r: Result ⇒ state.appendResultToFpe2Sci(defSite, r) - case _ ⇒ + case _ ⇒ processedDefSites.remove(defSite) } result case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) } } + /** + * Helper function for interpreting [[VirtualFunctionCall]]s. + */ + private def processVFC( + expr: VirtualFunctionCall[V], + defSite: Int, + params: List[Seq[StringConstancyInformation]] + ): ProperPropertyComputationResult = { + val r = new VirtualFunctionCallPreparationInterpreter( + cfg, this, ps, state, declaredMethods, params, c + ).interpret(expr, defSite) + // Set whether the virtual function call is fully prepared. This is the case if 1) the + // call was not fully prepared before (no final result available) or 2) the preparation is + // now done (methodPrep2defSite makes sure we have the TAC ready for a method required by + // this virtual function call). + if (!r.isInstanceOf[Result] && !state.isVFCFullyPrepared.contains(expr)) { + state.isVFCFullyPrepared(expr) = false + } else if (state.isVFCFullyPrepared.contains(expr) && state.methodPrep2defSite.isEmpty) { + state.isVFCFullyPrepared(expr) = true + } + val isPrepDone = !state.isVFCFullyPrepared.contains(expr) || state.isVFCFullyPrepared(expr) + + // In case no final result could be computed, remove this def site from the list of + // processed def sites to make sure that is can be compute again (when all final + // results are available); we use nonFinalFunctionArgs because if it does not + // contain expr, it can be finalized later on without processing the function again. + // A differentiation between "toString" and other calls is made since toString calls are not + // prepared in the same way as other calls are as toString does not take any arguments that + // might need to be prepared (however, toString needs a finalization procedure) + if (expr.name == "toString" && + (state.nonFinalFunctionArgs.contains(expr) || !r.isInstanceOf[Result])) { + processedDefSites.remove(defSite) + } else if (state.nonFinalFunctionArgs.contains(expr) || !isPrepDone) { + processedDefSites.remove(defSite) + } + r + } + /** * This function takes parameters and a definition site and extracts the desired parameter from * the given list of parameters. Note that `defSite` is required to be <= -2. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 382cde247d..db4b48e311 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -56,8 +56,9 @@ class InterproceduralNonVirtualFunctionCallInterpreter( } val m = methods._1.head - val tac = getTACAI(ps, m, state) + val (tacEps, tac) = getTACAI(ps, m, state) if (tac.isDefined) { + state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar @@ -80,6 +81,8 @@ class InterproceduralNonVirtualFunctionCallInterpreter( } } else { // No TAC => Register dependee and continue + state.appendToMethodPrep2defSite(m, defSite) + state.dependees = tacEps :: state.dependees InterimResult( state.entity, StringConstancyProperty.lb, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 6493b1dc42..4acc10c938 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -10,14 +10,19 @@ import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.Method import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.string_analysis import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.string_analysis.P /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -40,6 +45,15 @@ class InterproceduralStaticFunctionCallInterpreter( override type T = StaticFunctionCall[V] + private def extractReturnsFromFunction( + tac: TACode[TACMethodParameter, string_analysis.V], + m: Method + ): P = { + val ret = tac.stmts.find(_.isInstanceOf[ReturnValue[V]]).get + val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + (uvar, m) + } + /** * This function always returns a list with a single element consisting of * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]], @@ -63,7 +77,7 @@ class InterproceduralStaticFunctionCallInterpreter( } val m = methods._1.head - val tac = getTACAI(ps, m, state) + val (tacEps, tac) = getTACAI(ps, m, state) val directCallSites = state.callees.directCallSites()(ps, declaredMethods) val relevantPCs = directCallSites.filter { @@ -89,7 +103,14 @@ class InterproceduralStaticFunctionCallInterpreter( // Continue only when all parameter information are available val nonFinalResults = getNonFinalParameters(params) if (nonFinalResults.nonEmpty) { + if (tac.isDefined) { + val e = extractReturnsFromFunction(tac.get, m) + val eps = ps(e, StringConstancyProperty.key) + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(e._1, defSite) + } state.nonFinalFunctionArgs(instr) = params + state.appendToMethodPrep2defSite(m, defSite) return nonFinalResults.head } @@ -97,10 +118,9 @@ class InterproceduralStaticFunctionCallInterpreter( state.nonFinalFunctionArgsPos.remove(instr) val evaluatedParams = convertEvaluatedParameters(params) if (tac.isDefined) { + state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis - val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get - val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar - val entity = (uvar, m) + val entity = extractReturnsFromFunction(tac.get, m) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) @@ -109,7 +129,7 @@ class InterproceduralStaticFunctionCallInterpreter( Result(e, p) case _ ⇒ state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(uvar, defSite) + state.appendToVar2IndexMapping(entity._1, defSite) InterimResult( entity, StringConstancyProperty.lb, @@ -119,8 +139,16 @@ class InterproceduralStaticFunctionCallInterpreter( ) } } else { - // No TAC (e.g., for native methods) - Result(instr, StringConstancyProperty.lb) + // No TAC => Register dependee and continue + state.appendToMethodPrep2defSite(m, defSite) + state.dependees = tacEps :: state.dependees + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + c + ) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index d27adc78ab..26c140762a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -147,8 +147,9 @@ class VirtualFunctionCallPreparationInterpreter( state.nonFinalFunctionArgsPos.remove(instr) val evaluatedParams = convertEvaluatedParameters(params) val results = methods.map { nextMethod ⇒ - val tac = getTACAI(ps, nextMethod, state) + val (tacEps, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { + state.methodPrep2defSite.remove(nextMethod) // It might be that a function has no return value, e. g., in case it is guaranteed // to throw an exception (see, e.g., // com.sun.org.apache.xpath.internal.objects.XRTreeFragSelectWrapper#str) @@ -179,8 +180,16 @@ class VirtualFunctionCallPreparationInterpreter( } } } else { - // No TAC (e.g., for native methods) - Result(instr, StringConstancyProperty.lb) + // No TAC => Register dependee and continue + state.appendToMethodPrep2defSite(nextMethod, defSite) + state.dependees = tacEps :: state.dependees + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + c + ) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 85848f7217..64fb9a91de 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -99,6 +99,11 @@ class VirtualFunctionCallFinalizer( val finalSci = StringConstancyInformation.reduceMultiple( dependeeSites.toArray.flatMap { ds ⇒ state.fpe2sci(ds) } ) + // Remove the dependees, such as calls to "toString"; the reason being is that we do not + // duplications (arising from an "append" and a "toString" call) + dependeeSites.foreach { + state.appendToFpe2Sci(_, StringConstancyInformation.getNeutralElement, reset = true) + } state.appendToFpe2Sci(defSite, finalSci) } From 25b7065d6c8a8163aec61418c440ff3dab4fcf74 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 11:19:20 +0100 Subject: [PATCH 245/316] Restructured the file helper methods. --- .../InterproceduralTestMethods.java | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 12ef199b23..8489880298 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -322,7 +322,8 @@ public void dependenciesWithinFinalizeTest(String s, Class clazz) { m = clazz.getMethod(getterName); System.out.println(m); analyzeString(getterName); - } catch (NoSuchMethodException var13) {} + } catch (NoSuchMethodException var13) { + } } @StringDefinitionsCollection( @@ -340,6 +341,21 @@ public String callerWithFunctionParameterTest(String s, float i) { return s; } + /** + * Necessary for the callerWithFunctionParameterTest. + */ + public void belongsToSomeTestCase() { + String s = callerWithFunctionParameterTest(belongsToTheSameTestCase(), 900); + System.out.println(s); + } + + /** + * Necessary for the callerWithFunctionParameterTest. + */ + public static String belongsToTheSameTestCase() { + return getHelloWorld(); + } + @StringDefinitionsCollection( value = "a case where a function takes another function as one of its parameters", stringDefinitions = { @@ -415,6 +431,13 @@ public String tieName(String var0, Remote remote) { return s; } + /** + * Necessary for the tieName test. + */ + private static String tieNameForCompiler(String var0) { + return var0 + "_tie"; + } + @StringDefinitionsCollection( value = "a case where a string field is read (the expected strings should actually" + "contain the value written in belongsToFieldReadTest!)", @@ -448,6 +471,7 @@ public void fieldWithNoWriteTest() { // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { +// @StringDefinitions( // expectedLevel = CONSTANT, // expectedStrings = "value" // ) @@ -457,28 +481,14 @@ public void fieldWithNoWriteTest() { // analyzeString(value); // return value; // } - - /** - * Necessary for the callerWithFunctionParameterTest. - */ - public void belongsToSomeTestCase() { - String s = callerWithFunctionParameterTest(belongsToTheSameTestCase(), 900); - System.out.println(s); - } - - /** - * Necessary for the callerWithFunctionParameterTest. - */ - public static String belongsToTheSameTestCase() { - return getHelloWorld(); - } - - /** - * Necessary for the tieName test. - */ - private static String tieNameForCompiler(String var0) { - return var0 + "_tie"; - } +// +// private String getProperty(String name) { +// if (name == null) { +// return cyclicDependencyTest("default"); +// } else { +// return "value"; +// } +// } private String getRuntimeClassName() { return "java.lang.Runtime"; @@ -504,12 +514,4 @@ private String addQuestionMark(String s) { return s + "?"; } -// private String getProperty(String name) { -// if (name == null) { -// return cyclicDependencyTest("default"); -// } else { -// return "value"; -// } -// } - } From cefaa3a1b096375362f69aa94e2a43a6cfffcc1d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 13:25:14 +0100 Subject: [PATCH 246/316] Simplified a routine. --- .../fixtures/string_analysis/InterproceduralTestMethods.java | 4 ++-- .../string_analysis/InterproceduralStringAnalysis.scala | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 8489880298..af28f302c1 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -131,8 +131,8 @@ public void methodOutOfScopeTest() throws FileNotFoundException { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|\\w|java.lang.(Integer|" - + "Object|Runtime)|\\w)" + expectedStrings = "(java.lang.Object|java.lang.Runtime|" + + "java.lang.(Integer|Object|Runtime)|\\w)" ) }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 2e4b5d9bd0..d920970a3d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -233,12 +233,10 @@ class InterproceduralStringAnalysis( } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { + if (computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci ).reduce(true) - } else { - return getInterimResult(state) } // No need to cover the else branch: interimResults.nonEmpty => dependees were added to // state.dependees, i.e., the if that checks whether state.dependees is non-empty will From e93483bd3474b9fd26e94be0993e6c32b418c1b6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 15:32:13 +0100 Subject: [PATCH 247/316] Put an EPS to the dependees list only if a non-final result could be determined. --- .../interpretation/AbstractStringInterpreter.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 4cd3d7e5af..1125a1de0d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -73,7 +73,9 @@ abstract class AbstractStringInterpreter( if (tacai.hasUBP) { (tacai, tacai.ub.tac) } else { - s.dependees = tacai :: s.dependees + if (tacai.isRefinable) { + s.dependees = tacai :: s.dependees + } (tacai, None) } } From f7724d0cc99263af33b4cd467cbfaf03348d69ff Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 16:14:37 +0100 Subject: [PATCH 248/316] Do not add TAC entities twice to the property store. --- .../InterproceduralNonVirtualFunctionCallInterpreter.scala | 4 +--- .../InterproceduralStaticFunctionCallInterpreter.scala | 3 +-- .../VirtualFunctionCallPreparationInterpreter.scala | 4 +--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index db4b48e311..2332e29415 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -56,7 +56,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( } val m = methods._1.head - val (tacEps, tac) = getTACAI(ps, m, state) + val (_, tac) = getTACAI(ps, m, state) if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis @@ -80,9 +80,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( ) } } else { - // No TAC => Register dependee and continue state.appendToMethodPrep2defSite(m, defSite) - state.dependees = tacEps :: state.dependees InterimResult( state.entity, StringConstancyProperty.lb, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 4acc10c938..00320e82e6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -77,7 +77,7 @@ class InterproceduralStaticFunctionCallInterpreter( } val m = methods._1.head - val (tacEps, tac) = getTACAI(ps, m, state) + val (_, tac) = getTACAI(ps, m, state) val directCallSites = state.callees.directCallSites()(ps, declaredMethods) val relevantPCs = directCallSites.filter { @@ -141,7 +141,6 @@ class InterproceduralStaticFunctionCallInterpreter( } else { // No TAC => Register dependee and continue state.appendToMethodPrep2defSite(m, defSite) - state.dependees = tacEps :: state.dependees InterimResult( state.entity, StringConstancyProperty.lb, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 26c140762a..8b7b1970b3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -147,7 +147,7 @@ class VirtualFunctionCallPreparationInterpreter( state.nonFinalFunctionArgsPos.remove(instr) val evaluatedParams = convertEvaluatedParameters(params) val results = methods.map { nextMethod ⇒ - val (tacEps, tac) = getTACAI(ps, nextMethod, state) + val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { state.methodPrep2defSite.remove(nextMethod) // It might be that a function has no return value, e. g., in case it is guaranteed @@ -180,9 +180,7 @@ class VirtualFunctionCallPreparationInterpreter( } } } else { - // No TAC => Register dependee and continue state.appendToMethodPrep2defSite(nextMethod, defSite) - state.dependees = tacEps :: state.dependees InterimResult( state.entity, StringConstancyProperty.lb, From 4ca88eaaec113933dd95b094d62a328a242c3e8d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 17:10:39 +0100 Subject: [PATCH 249/316] Avoided code duplication. --- .../InterproceduralTestMethods.java | 4 ++-- .../InterproceduralStringAnalysis.scala | 16 +++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index af28f302c1..8489880298 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -131,8 +131,8 @@ public void methodOutOfScopeTest() throws FileNotFoundException { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|java.lang.Runtime|" - + "java.lang.(Integer|Object|Runtime)|\\w)" + expectedStrings = "(java.lang.Object|\\w|java.lang.(Integer|" + + "Object|Runtime)|\\w)" ) }) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index d920970a3d..df95e8be70 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -211,6 +211,7 @@ class InterproceduralStringAnalysis( } val call = stmts(defSites.head).asAssignment.expr + var attemptFinalResultComputation = false if (InterpretationHandler.isStringBuilderBufferToStringCall(call)) { // Find DUVars, that the analysis of the current entity depends on val dependentVars = findDependentVars(state.computedLeanPath, stmts, uvar) @@ -225,22 +226,19 @@ class InterproceduralStringAnalysis( } } } else { - if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { - sci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci - ).reduce(true) - } + attemptFinalResultComputation = true } } // If not a call to String{Builder, Buffer}.toString, then we deal with pure strings else { - if (computeResultsForPath(state.computedLeanPath, state)) { + attemptFinalResultComputation = true + } + + if (attemptFinalResultComputation) { + if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { sci = new PathTransformer(state.iHandler).pathToStringTree( state.computedLeanPath, state.fpe2sci ).reduce(true) } - // No need to cover the else branch: interimResults.nonEmpty => dependees were added to - // state.dependees, i.e., the if that checks whether state.dependees is non-empty will - // always be true (thus, the value of "sci" does not matter) } if (state.dependees.nonEmpty) { From aacc946879d5a832d5ce4581c0007b797eb64838 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 19:27:57 +0100 Subject: [PATCH 250/316] Avoided some code duplication + cyclic dependencies are now resolved. --- .../InterproceduralStringAnalysis.scala | 188 +++++++++--------- 1 file changed, 93 insertions(+), 95 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index df95e8be70..2ee51a8dee 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -6,6 +6,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Property @@ -82,16 +83,15 @@ class InterproceduralStringAnalysis( private final val fieldAccessInformation = project.get(FieldAccessInformationKey) /** - * Returns the current interim result for the given state. + * Returns the current interim result for the given state. If required, custom lower and upper + * bounds can be used for the interim result. */ private def getInterimResult( - state: InterproceduralComputationState + state: InterproceduralComputationState, + lb: StringConstancyProperty = StringConstancyProperty.lb, + ub: StringConstancyProperty = StringConstancyProperty.ub ): InterimResult[StringConstancyProperty] = InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - continuation(state) + state.entity, lb, ub, state.dependees, continuation(state) ) def analyze(data: P): ProperPropertyComputationResult = { @@ -260,103 +260,101 @@ class InterproceduralStringAnalysis( */ private def continuation( state: InterproceduralComputationState - )(eps: SomeEPS): ProperPropertyComputationResult = eps.pk match { - case TACAI.key ⇒ eps match { - case FinalP(tac: TACAI) ⇒ - // Set the TAC only once (the TAC might be requested for other methods, so this - // makes sure we do not overwrite the state's TAC) - if (state.tac == null) { - state.tac = tac.tac.get - } - state.dependees = state.dependees.filter(_.e != eps.e) - determinePossibleStrings(state) - case _ ⇒ - state.dependees = state.dependees.filter(_.e != eps.e) - state.dependees = eps :: state.dependees - getInterimResult(state) - } - case Callees.key ⇒ eps match { - case FinalP(callees: Callees) ⇒ - state.callees = callees - state.dependees = state.dependees.filter(_.e != eps.e) - if (state.dependees.isEmpty) { - determinePossibleStrings(state) - } else { - getInterimResult(state) - } - case _ ⇒ - state.dependees = state.dependees.filter(_.e != eps.e) - state.dependees = eps :: state.dependees - getInterimResult(state) - } - case CallersProperty.key ⇒ eps match { - case FinalP(callers: CallersProperty) ⇒ - state.callers = callers - state.dependees = state.dependees.filter(_.e != eps.e) - if (state.dependees.isEmpty) { - registerParams(state) + )(eps: SomeEPS): ProperPropertyComputationResult = { + state.dependees = state.dependees.filter(_.e ne eps.e) + eps.pk match { + case TACAI.key ⇒ eps match { + case FinalP(tac: TACAI) ⇒ + // Set the TAC only once (the TAC might be requested for other methods, so this + // makes sure we do not overwrite the state's TAC) + if (state.tac == null) { + state.tac = tac.tac.get + } determinePossibleStrings(state) - } else { + case _ ⇒ + state.dependees = eps :: state.dependees getInterimResult(state) - } - case _ ⇒ - state.dependees = state.dependees.filter(_.e != eps.e) - state.dependees = eps :: state.dependees - getInterimResult(state) - } - case StringConstancyProperty.key ⇒ - eps match { - case FinalP(p: StringConstancyProperty) ⇒ - val resultEntity = eps.e.asInstanceOf[P] - // If necessary, update the parameter information with which the - // surrounding function / method of the entity was called with - if (state.paramResultPositions.contains(resultEntity)) { - val pos = state.paramResultPositions(resultEntity) - state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(resultEntity) - state.parameterDependeesCount -= 1 - state.dependees = state.dependees.filter(_.e != eps.e) - } - - // If necessary, update parameter information of function calls - if (state.entity2Function.contains(resultEntity)) { - state.var2IndexMapping(resultEntity._1).foreach(state.appendToFpe2Sci( - _, p.stringConstancyInformation - )) - // Update the state - state.entity2Function(resultEntity).foreach { f ⇒ - val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) - val result = Result(resultEntity, p) - state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result - // TODO: Is that correct? (rather remove only the function from the list) - state.entity2Function.remove(resultEntity) - } - // Continue only after all necessary function parameters are evaluated - if (state.entity2Function.nonEmpty) { - return getInterimResult(state) - } else { - // We could try to determine a final result before all function - // parameter information are available, however, this will - // definitely result in finding some intermediate result. Thus, - // defer this computations when we know that all necessary - // information are available - state.entity2Function.clear() - if (!computeResultsForPath(state.computedLeanPath, state)) { - return determinePossibleStrings(state) - } - } - } - - if (state.isSetupCompleted && state.parameterDependeesCount == 0) { - processFinalP(state, eps.e, p) + } + case Callees.key ⇒ eps match { + case FinalP(callees: Callees) ⇒ + state.callees = callees + if (state.dependees.isEmpty) { + determinePossibleStrings(state) } else { + getInterimResult(state) + } + case _ ⇒ + state.dependees = eps :: state.dependees + getInterimResult(state) + } + case CallersProperty.key ⇒ eps match { + case FinalP(callers: CallersProperty) ⇒ + state.callers = callers + if (state.dependees.isEmpty) { + registerParams(state) determinePossibleStrings(state) + } else { + getInterimResult(state) } case _ ⇒ - state.dependees = state.dependees.filter(_.e != eps.e) state.dependees = eps :: state.dependees getInterimResult(state) } + case StringConstancyProperty.key ⇒ + eps match { + case FinalP(p: StringConstancyProperty) ⇒ + val resultEntity = eps.e.asInstanceOf[P] + // If necessary, update the parameter information with which the + // surrounding function / method of the entity was called with + if (state.paramResultPositions.contains(resultEntity)) { + val pos = state.paramResultPositions(resultEntity) + state.params(pos._1)(pos._2) = p.stringConstancyInformation + state.paramResultPositions.remove(resultEntity) + state.parameterDependeesCount -= 1 + } + + // If necessary, update parameter information of function calls + if (state.entity2Function.contains(resultEntity)) { + state.var2IndexMapping(resultEntity._1).foreach(state.appendToFpe2Sci( + _, p.stringConstancyInformation + )) + // Update the state + state.entity2Function(resultEntity).foreach { f ⇒ + val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) + val result = Result(resultEntity, p) + state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result + // TODO: Is that correct? (rather remove only the function from the list) + state.entity2Function.remove(resultEntity) + } + // Continue only after all necessary function parameters are evaluated + if (state.entity2Function.nonEmpty) { + return getInterimResult(state) + } else { + // We could try to determine a final result before all function + // parameter information are available, however, this will + // definitely result in finding some intermediate result. Thus, + // defer this computations when we know that all necessary + // information are available + state.entity2Function.clear() + if (!computeResultsForPath(state.computedLeanPath, state)) { + return determinePossibleStrings(state) + } + } + } + + if (state.isSetupCompleted && state.parameterDependeesCount == 0) { + processFinalP(state, eps.e, p) + } else { + determinePossibleStrings(state) + } + case InterimLUBP(lb: StringConstancyProperty, ub: StringConstancyProperty) ⇒ + state.dependees = eps :: state.dependees + getInterimResult(state, lb, ub) + case _ ⇒ + state.dependees = eps :: state.dependees + getInterimResult(state) + } + } } private def finalizePreparations( From de03a45e4a2918c8dbbd0e9e79dc33e9473e5406 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 20:56:49 +0100 Subject: [PATCH 251/316] Updated a comment. --- .../finalizer/VirtualFunctionCallFinalizer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index 64fb9a91de..adaa8635c6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -22,8 +22,8 @@ class VirtualFunctionCallFinalizer( override type T = VirtualFunctionCall[V] /** - * Finalizes [[VirtualFunctionCall]]s. Currently, this finalizer supports only the "append" - * function. + * Finalizes [[VirtualFunctionCall]]s. Currently, this finalizer supports only the "append" and + * "toString" function. *

    * @inheritdoc */ From cb8689fff08ddb14fcd1a499dcc4f6a597aba68e Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 20:57:28 +0100 Subject: [PATCH 252/316] Refined the handling of field read operations. --- .../InterproceduralTestMethods.java | 7 +- .../InterproceduralFieldInterpreter.scala | 82 +++++++++++++------ ...InterproceduralInterpretationHandler.scala | 5 ++ .../finalizer/GetFieldFinalizer.scala | 33 ++++++++ 4 files changed, 96 insertions(+), 31 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 8489880298..f555c443ff 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -439,12 +439,11 @@ private static String tieNameForCompiler(String var0) { } @StringDefinitionsCollection( - value = "a case where a string field is read (the expected strings should actually" - + "contain the value written in belongsToFieldReadTest!)", + value = "a case where a string field is read", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "(\\w|some value)" + expectedLevel = CONSTANT, + expectedStrings = "(some value|another value)" ) }) public void fieldReadTest() { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 79bb71514a..44ecef82b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -52,52 +52,80 @@ class InterproceduralFieldInterpreter( */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { val defSitEntity: Integer = defSite - if (InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { - val results = ListBuffer[ProperPropertyComputationResult]() - fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { - case (m, pcs) ⇒ pcs.foreach { pc ⇒ - getTACAI(ps, m, state) match { + if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { + // Unknown type => Cannot further approximate + Result(instr, StringConstancyProperty.lb) + } + + val results = ListBuffer[ProperPropertyComputationResult]() + fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { + case (m, pcs) ⇒ pcs.foreach { pc ⇒ + val (tacEps, tac) = getTACAI(ps, m, state) + val nextResult = if (tacEps.isRefinable) { + InterimResult( + instr, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + c + ) + } else { + tac match { case Some(methodTac) ⇒ val stmt = methodTac.stmts(methodTac.pcToIndex(pc)) val entity = (stmt.asPutField.value.asVar, m) val eps = ps(entity, StringConstancyProperty.key) - results.append(eps match { + eps match { case FinalEP(e, p) ⇒ Result(e, p) case _ ⇒ state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) + // We need some mapping from an entity to an index in order for + // the processFinalP to find an entry. We cannot use the given + // def site as this would mark the def site as finalized even + // though it might not be. Thus, we use -1 as it is a safe dummy + // value + state.appendToVar2IndexMapping(entity._1, -1) InterimResult( entity, StringConstancyProperty.lb, StringConstancyProperty.ub, - List(), + state.dependees, c ) - }) + } case _ ⇒ - state.appendToFpe2Sci( - defSitEntity, StringConstancyProperty.lb.stringConstancyInformation - ) - results.append(Result(defSitEntity, StringConstancyProperty.lb)) + // No TAC available + Result(defSitEntity, StringConstancyProperty.lb) } } + results.append(nextResult) } - if (results.isEmpty) { - // No methods, which write the field, were found => Field could either be null or - // any value - val possibleStrings = "(^null$|"+StringConstancyInformation.UnknownWordSymbol+")" - val sci = StringConstancyInformation( - StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings - ) - Result(defSitEntity, StringConstancyProperty(sci)) + } + + if (results.isEmpty) { + // No methods, which write the field, were found => Field could either be null or + // any value + val possibleStrings = "(^null$|"+StringConstancyInformation.UnknownWordSymbol+")" + val sci = StringConstancyInformation( + StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings + ) + state.appendToFpe2Sci( + defSitEntity, StringConstancyProperty.lb.stringConstancyInformation + ) + Result(defSitEntity, StringConstancyProperty(sci)) + } else { + // If all results are final, determine all possible values for the field. Otherwise, + // return some intermediate result to indicate that the computation is not yet done + if (results.forall(_.isInstanceOf[Result])) { + val resultScis = results.map { + StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation + } + val finalSci = StringConstancyInformation.reduceMultiple(resultScis) + state.appendToFpe2Sci(defSitEntity, finalSci) + Result(defSitEntity, StringConstancyProperty(finalSci)) } else { - // If available, return an intermediate result to indicate that the computation - // needs to be continued - results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + results.find(!_.isInstanceOf[Result]).get } - } else { - // Unknown type => Cannot further approximate - Result(instr, StringConstancyProperty.lb) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 7f697a7cdf..f4f6896ac7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -42,6 +42,7 @@ import org.opalj.tac.DUVar import org.opalj.tac.GetStatic import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.GetFieldFinalizer /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -249,6 +250,10 @@ class InterproceduralInterpretationHandler( VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) + case Assignment(_, _, gf: GetField[V]) ⇒ + GetFieldFinalizer(state).finalizeInterpretation(gf, defSite) + case ExprStmt(_, gf: GetField[V]) ⇒ + GetFieldFinalizer(state).finalizeInterpretation(gf, defSite) case _ ⇒ state.appendToFpe2Sci( defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala new file mode 100644 index 0000000000..56f11f0beb --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala @@ -0,0 +1,33 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer + +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.GetField + +class GetFieldFinalizer( + state: InterproceduralComputationState +) extends AbstractFinalizer(state) { + + override protected type T = GetField[V] + + /** + * Finalizes [[GetField]]s. + *

    + * @inheritdoc + */ + override def finalizeInterpretation(instr: T, defSite: Int): Unit = + // Processing the definition site again is enough as the finalization procedure is only + // called after all dependencies are resolved. Thus, processing the given def site with + // produce a result + state.iHandler.processDefSite(defSite) + +} + +object GetFieldFinalizer { + + def apply( + state: InterproceduralComputationState + ): GetFieldFinalizer = new GetFieldFinalizer(state) + +} From 3a1fe05e54b9fc76a50bfd61f508e90d557b7908 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 21:09:30 +0100 Subject: [PATCH 253/316] Added a test case where a field of an unsupported type is read. --- .../InterproceduralTestMethods.java | 14 ++++++++++++++ .../InterproceduralFieldInterpreter.scala | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index f555c443ff..f55c196a5d 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -32,6 +32,8 @@ public class InterproceduralTestMethods { private String noWriteField; + private Object myObject; + /** * {@see LocalTestMethods#analyzeString} */ @@ -467,6 +469,18 @@ public void fieldWithNoWriteTest() { analyzeString(noWriteField); } + @StringDefinitionsCollection( + value = "a case where a field is read whose type is not supported", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" + ) + }) + public void nonSupportedFieldTypeRead() { + analyzeString(myObject.toString()); + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 44ecef82b1..9c44cb939d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -54,7 +54,7 @@ class InterproceduralFieldInterpreter( val defSitEntity: Integer = defSite if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { // Unknown type => Cannot further approximate - Result(instr, StringConstancyProperty.lb) + return Result(instr, StringConstancyProperty.lb) } val results = ListBuffer[ProperPropertyComputationResult]() From b72884a8f0f92be7917aa93d9686ef1d19d0eef6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 21:32:01 +0100 Subject: [PATCH 254/316] Refined the handling of fields in the sense that no initialization (either at the field or in the constructor) leads to adding a null element. --- .../InterproceduralTestMethods.java | 34 ++++++++++++++++++- .../StringConstancyInformation.scala | 11 ++++++ .../InterproceduralFieldInterpreter.scala | 12 +++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index f55c196a5d..9b6ab82a17 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -34,12 +34,20 @@ public class InterproceduralTestMethods { private Object myObject; + private String fieldWithInit = "init field value"; + + private String fieldWithConstructorInit; + /** * {@see LocalTestMethods#analyzeString} */ public void analyzeString(String s) { } + public InterproceduralTestMethods() { + fieldWithConstructorInit = "initialized by constructor"; + } + @StringDefinitionsCollection( value = "a case where a very simple non-virtual function call is interpreted", stringDefinitions = { @@ -445,7 +453,7 @@ private static String tieNameForCompiler(String var0) { stringDefinitions = { @StringDefinitions( expectedLevel = CONSTANT, - expectedStrings = "(some value|another value)" + expectedStrings = "(some value|another value|^null$)" ) }) public void fieldReadTest() { @@ -481,6 +489,30 @@ public void nonSupportedFieldTypeRead() { analyzeString(myObject.toString()); } + @StringDefinitionsCollection( + value = "a case where a field is declared and initialized", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "init field value" + ) + }) + public void fieldWithInitTest() { + analyzeString(fieldWithInit.toString()); + } + + @StringDefinitionsCollection( + value = "a case where a field is initialized in a constructor", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "initialized by constructor" + ) + }) + public void fieldInitByConstructor() { + analyzeString(fieldWithConstructorInit.toString()); + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 4164d7756e..d75e032fe0 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -53,6 +53,11 @@ object StringConstancyInformation { */ val InfiniteRepetitionSymbol: String = "*" + /** + * A value to be used to indicate that a string expression might be null. + */ + val NullStringValue: String = "^null$" + /** * Takes a list of [[StringConstancyInformation]] and reduces them to a single one by or-ing * them together (the level is determined by finding the most general level; the type is set to @@ -95,4 +100,10 @@ object StringConstancyInformation { def getNeutralElement: StringConstancyInformation = StringConstancyInformation(StringConstancyLevel.CONSTANT) + /** + * @return Returns a [[StringConstancyInformation]] element to indicate a `null` value. + */ + def getNullElement: StringConstancyInformation = + StringConstancyInformation(StringConstancyLevel.CONSTANT, possibleStrings = NullStringValue) + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 9c44cb939d..d1000f5bbc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -57,9 +57,13 @@ class InterproceduralFieldInterpreter( return Result(instr, StringConstancyProperty.lb) } + var hasInit = false val results = ListBuffer[ProperPropertyComputationResult]() fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { case (m, pcs) ⇒ pcs.foreach { pc ⇒ + if (m.name == "") { + hasInit = true + } val (tacEps, tac) = getTACAI(ps, m, state) val nextResult = if (tacEps.isRefinable) { InterimResult( @@ -117,6 +121,14 @@ class InterproceduralFieldInterpreter( // If all results are final, determine all possible values for the field. Otherwise, // return some intermediate result to indicate that the computation is not yet done if (results.forall(_.isInstanceOf[Result])) { + // No init is present => append a `null` element to indicate that the field might be + // null; this behavior could be refined by only setting the null element if no + // statement is guaranteed to be executed prior to the field read + if (!hasInit) { + results.append(Result( + instr, StringConstancyProperty(StringConstancyInformation.getNullElement) + )) + } val resultScis = results.map { StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation } From d8bceffed2b0c583a1637b083359a428d01469be Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 22:26:19 +0100 Subject: [PATCH 255/316] Extended the support for primitive number types. --- .../InterproceduralStringAnalysis.scala | 47 ++++++++++++++++++- .../InterpretationHandler.scala | 10 ++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 2ee51a8dee..eaad0ea075 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -26,6 +26,7 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.FieldType import org.opalj.br.analyses.FieldAccessInformationKey +import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathElement @@ -157,6 +158,11 @@ class InterproceduralStringAnalysis( requiresCallersInfo = if (defSites.exists(_ < 0)) { if (InterpretationHandler.isStringConstExpression(uvar)) { hasCallersOrParamInfo + } else if (InterproceduralStringAnalysis.isSupportedPrimitiveNumberType(uvar)) { + val numType = uvar.value.asPrimitiveValue.primitiveType.toJava + val sci = InterproceduralStringAnalysis. + getDynamicStringInformationForNumberType(numType) + return Result(state.entity, StringConstancyProperty(sci)) } else { // StringBuilders as parameters are currently not evaluated return Result(state.entity, StringConstancyProperty.lb) @@ -707,6 +713,26 @@ object InterproceduralStringAnalysis { ListBuffer() } + /** + * This function checks whether a given type is a supported primitive type. Supported currently + * means short, int, float, or double. + */ + def isSupportedPrimitiveNumberType(v: V): Boolean = { + val value = v.value + if (value.isPrimitiveValue) { + isSupportedPrimitiveNumberType(value.asPrimitiveValue.primitiveType.toJava) + } else { + false + } + } + + /** + * This function checks whether a given type is a supported primitive type. Supported currently + * means short, int, float, or double. + */ + def isSupportedPrimitiveNumberType(typeName: String): Boolean = + typeName == "short" || typeName == "int" || typeName == "float" || typeName == "double" + /** * Checks whether a given type, identified by its string representation, is supported by the * string analysis. That means, if this function returns `true`, a value, which is of type @@ -718,8 +744,8 @@ object InterproceduralStringAnalysis { * java.lang.String] and `false` otherwise. */ def isSupportedType(typeName: String): Boolean = - typeName == "char" || typeName == "short" || typeName == "int" || typeName == "float" || - typeName == "double" || typeName == "java.lang.String" + typeName == "char" || isSupportedPrimitiveNumberType(typeName) || + typeName == "java.lang.String" /** * Determines whether a given [[V]] element ([[DUVar]]) is supported by the string analysis. @@ -748,6 +774,23 @@ object InterproceduralStringAnalysis { */ def isSupportedType(fieldType: FieldType): Boolean = isSupportedType(fieldType.toJava) + /** + * Takes the name of a primitive number type - supported types are short, int, float, double - + * and returns the dynamic [[StringConstancyInformation]] for that type. In case an unsupported + * type is given [[StringConstancyInformation.UnknownWordSymbol]] is returned as possible + * strings. + */ + def getDynamicStringInformationForNumberType( + numberType: String + ): StringConstancyInformation = { + val possibleStrings = numberType match { + case "short" | "int" ⇒ StringConstancyInformation.IntValue + case "float" | "double" ⇒ StringConstancyInformation.FloatValue + case _ ⇒ StringConstancyInformation.UnknownWordSymbol + } + StringConstancyInformation(StringConstancyLevel.DYNAMIC, possibleStrings = possibleStrings) + } + } sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisScheduler { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 235e3704de..14da3b0b8d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -22,6 +22,7 @@ import org.opalj.tac.GetField import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[ValueInformation]]) { @@ -114,6 +115,15 @@ object InterpretationHandler { } } + /** + * Returns `true` if the given expressions is a primitive number type + */ + def isPrimitiveNumberTypeExpression(expr: Expr[V]): Boolean = + expr.asVar.value.isPrimitiveValue && + InterproceduralStringAnalysis.isSupportedPrimitiveNumberType( + expr.asVar.value.asPrimitiveValue.primitiveType.toJava + ) + /** * Checks whether an expression contains a call to [[StringBuilder#append]] or * [[StringBuffer#append]]. From 6fa72fd899c4ad531f7b140579fe4962dd51746b Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 8 Mar 2019 22:28:25 +0100 Subject: [PATCH 256/316] Added a test case where a field is initialized using a constructor parameter. --- .../InterproceduralTestMethods.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 9b6ab82a17..3ba9106a70 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -38,14 +38,17 @@ public class InterproceduralTestMethods { private String fieldWithConstructorInit; + private float secretNumber; + /** * {@see LocalTestMethods#analyzeString} */ public void analyzeString(String s) { } - public InterproceduralTestMethods() { + public InterproceduralTestMethods(float e) { fieldWithConstructorInit = "initialized by constructor"; + secretNumber = e; } @StringDefinitionsCollection( @@ -513,6 +516,18 @@ public void fieldInitByConstructor() { analyzeString(fieldWithConstructorInit.toString()); } + @StringDefinitionsCollection( + value = "a case where a field is initialized with a value of a constructor parameter", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "^-?\\d*\\.{0,1}\\d+$" + ) + }) + public void fieldInitByConstructorParameter() { + analyzeString(new StringBuilder().append(secretNumber).toString()); + } + // @StringDefinitionsCollection( // value = "a case where no callers information need to be computed", // stringDefinitions = { From 2d1a202b031741b46e0a551067570af58cf6255b Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 9 Mar 2019 10:52:28 +0100 Subject: [PATCH 257/316] Slightly refined the procedure to detect conditionals with or without alternatives. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 030e711f7f..69304d9d9f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -711,8 +711,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val branches = successors.reverse.tail.reverse val lastEle = successors.last - // If an "if" ends at the end of a loop, it cannot have an else - if (cfg.findNaturalLoops().exists(_.last == lastEle - 1)) { + // If an "if" ends at the end of a loop (the "if" must be within that loop!), it cannot have + // an else + val loopOption = cfg.findNaturalLoops().find(_.last == lastEle - 1) + if (loopOption.isDefined && loopOption.get.head < branchingSite) { return true } From c3ac88a39fbcd491b047b8369d3d3e4babf08edb Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 9 Mar 2019 17:40:12 +0100 Subject: [PATCH 258/316] Some (minor) improvements / bug fixes on the path finding procedure. --- .../preprocessing/AbstractPathFinder.scala | 72 ++++++++++++++----- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 69304d9d9f..d923296543 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -89,17 +89,46 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (containsIf) { stack.push(nextBlock) } else { - cfg.code.instructions(nextBlock - 1) match { + // Check and find if there is a goto which provides further information about the + // bounds of the conditional; a goto is relevant, if it does not point back at a + // surrounding loop + var isRelevantGoto = false + val relevantGoTo: Option[Goto] = cfg.code.instructions(nextBlock - 1) match { case goto: Goto ⇒ - // Find the goto that points after the "else" part (the assumption is that - // this goto is the very last element of the current branch - endSite = goto.targetStmt - // The goto might point back at the beginning of a loop; if so, the end of - // the if/else is denoted by the end of the loop - if (endSite < branchingSite) { - endSite = cfg.findNaturalLoops().filter(_.head == endSite).head.last + // A goto is not relevant if it points at a loop that is within the + // conditional (this does not help / provides no further information) + val gotoSite = goto.targetStmt + isRelevantGoto = !cfg.findNaturalLoops().exists { l ⇒ + l.head == gotoSite + } + Some(goto) + case _ ⇒ None + } + + relevantGoTo match { + case Some(goto) ⇒ + if (isRelevantGoto) { + // Find the goto that points after the "else" part (the assumption is that + // this goto is the very last element of the current branch + endSite = goto.targetStmt + // The goto might point back at the beginning of a loop; if so, the end of + // the if/else is denoted by the end of the loop + if (endSite < branchingSite) { + endSite = cfg.findNaturalLoops().filter(_.head == endSite).head.last + } else { + endSite -= 1 + } } else { - endSite -= 1 + // If the conditional is encloses in a try-catch block, consider this + // bounds and otherwise the bounds of the surrounding element + cfg.bb(nextBlock).successors.find(_.isInstanceOf[CatchNode]) match { + case Some(cs: CatchNode) ⇒ endSite = cs.endPC + case _ ⇒ + endSite = if (nextBlock > branchingSite) nextBlock else + cfg.findNaturalLoops().find { + _.head == goto.targetStmt + }.get.last - 1 + } } case _ ⇒ // No goto available => Jump after next block @@ -158,13 +187,13 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // The second condition is necessary to detect two consecutive "if"s (not in an else-if // relation) if (cfg.code.instructions(i).isInstanceOf[If[V]] && ifTarget != i) { - processedIfs(i) = Unit nextIfIndex = i } } var endIndex = nextPossibleIfBlock - 1 - if (nextIfIndex > -1) { + if (nextIfIndex > -1 && !isHeadOfLoop(nextIfIndex, cfg.findNaturalLoops(), cfg)) { + processedIfs(nextIfIndex) = Unit val (_, newEndIndex) = getStartAndEndIndexOfCondWithoutAlternative( nextIfIndex, processedIfs ) @@ -198,7 +227,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { branchingSite >= cn.startPC && branchingSite <= cn.endPC } if (inTryBlocks.nonEmpty) { - endIndex = inTryBlocks.minBy(-_.startPC).endPC + val tryEndPC = inTryBlocks.minBy(-_.startPC).endPC + if (endIndex > tryEndPC) { + endIndex = tryEndPC + } } // It is now necessary to collect all ifs that belong to the whole if condition (in the @@ -708,7 +740,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } // Separate the last element from all previous ones - val branches = successors.reverse.tail.reverse + //val branches = successors.reverse.tail.reverse val lastEle = successors.last // If an "if" ends at the end of a loop (the "if" must be within that loop!), it cannot have @@ -723,7 +755,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val ifPos = bb.startPC.to(bb.endPC).filter( cfg.code.instructions(_).isInstanceOf[If[V]] ) - if (ifPos.nonEmpty) { + if (ifPos.nonEmpty && !isHeadOfLoop(ifPos.head, cfg.findNaturalLoops(), cfg)) { ifPos.head } else { -1 @@ -738,20 +770,22 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // For every successor (except the very last one), execute a DFS to check whether the // very last element is a successor. If so, this represents a path past the if (or // if-elseif). - branches.count { next ⇒ + var reachableCount = 0 + successors.foreach { next ⇒ val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) while (toVisitStack.nonEmpty) { val from = toVisitStack.pop() val to = from.successors - if (from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) { - return true + if ((from.nodeId == lastEle || to.contains(cfg.bb(lastEle))) && + from.nodeId >= branchingSite) { + reachableCount += 1 } seenNodes.append(from) toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) } - return false - } > 1 + } + reachableCount > 1 } } From fa304759cfaa071efbedb9b0ff86d77f14d41aa9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sat, 9 Mar 2019 20:46:21 +0100 Subject: [PATCH 259/316] Some (minor) improvements / bug fixes on the path finding procedure. --- .../preprocessing/AbstractPathFinder.scala | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index d923296543..f270490c8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -108,11 +108,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { relevantGoTo match { case Some(goto) ⇒ if (isRelevantGoto) { - // Find the goto that points after the "else" part (the assumption is that - // this goto is the very last element of the current branch + // Find the goto that points after the "else" part (the assumption is + // that this goto is the very last element of the current branch endSite = goto.targetStmt - // The goto might point back at the beginning of a loop; if so, the end of - // the if/else is denoted by the end of the loop + // The goto might point back at the beginning of a loop; if so, the end + // of the if/else is denoted by the end of the loop if (endSite < branchingSite) { endSite = cfg.findNaturalLoops().filter(_.head == endSite).head.last } else { @@ -124,10 +124,10 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { cfg.bb(nextBlock).successors.find(_.isInstanceOf[CatchNode]) match { case Some(cs: CatchNode) ⇒ endSite = cs.endPC case _ ⇒ - endSite = if (nextBlock > branchingSite) nextBlock else + endSite = if (nextBlock > branchingSite) nextBlock - 1 else cfg.findNaturalLoops().find { _.head == goto.targetStmt - }.get.last - 1 + }.get.last } } case _ ⇒ @@ -418,11 +418,19 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val stack = mutable.Stack[Int](start) while (stack.nonEmpty) { val popped = stack.pop() - val nextBlock = cfg.bb(popped).successors.map { + var nextBlock = cfg.bb(popped).successors.map { case bb: BasicBlock ⇒ bb.startPC // Handle Catch Nodes? case _ ⇒ -1 }.max + + if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { + nextBlock = popped + 1 + while (!cfg.code.instructions(nextBlock).isInstanceOf[If[V]]) { + nextBlock += 1 + } + } + var containsIf = false for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { if (cfg.code.instructions(i).isInstanceOf[If[V]]) { @@ -724,10 +732,15 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * other successors. If this is the case, the branching corresponds to one without an * ''else'' branch. */ - protected def isCondWithoutElse(branchingSite: Int, cfg: CFG[Stmt[V], TACStmts[V]]): Boolean = { + protected def isCondWithoutElse( + branchingSite: Int, + cfg: CFG[Stmt[V], TACStmts[V]], + processedIfs: mutable.Map[Int, Unit.type] + ): Boolean = { val successorBlocks = cfg.bb(branchingSite).successors // CatchNode exists => Regard it as conditional without alternative if (successorBlocks.exists(_.isInstanceOf[CatchNode])) { + processedIfs(branchingSite) = Unit return false } @@ -765,7 +778,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (indexIf != -1) { // For else-if constructs - isCondWithoutElse(indexIf, cfg) + isCondWithoutElse(indexIf, cfg, processedIfs) } else { // For every successor (except the very last one), execute a DFS to check whether the // very last element is a successor. If so, this represents a path past the if (or @@ -785,7 +798,12 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { toVisitStack.pushAll(to.filter(!seenNodes.contains(_))) } } - reachableCount > 1 + if (reachableCount > 1) { + true + } else { + processedIfs(branchingSite) = Unit + false + } } } @@ -876,7 +894,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { protected def processIf( stmt: Int, processedIfs: mutable.Map[Int, Unit.type] ): CSInfo = { - val csType = determineTypeOfIf(stmt) + val csType = determineTypeOfIf(stmt, processedIfs) val (startIndex, endIndex) = csType match { case NestedPathType.Repetition ⇒ processedIfs(stmt) = Unit @@ -937,13 +955,15 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * [[NestedPathType.TryCatchFinally]] (as their construction does not involve an [[If]] * statement). */ - protected def determineTypeOfIf(stmtIndex: Int): NestedPathType.Value = { + protected def determineTypeOfIf( + stmtIndex: Int, processedIfs: mutable.Map[Int, Unit.type] + ): NestedPathType.Value = { // Is the first condition enough to identify loops? val loops = cfg.findNaturalLoops() // The if might belong to the head or end of the loop if (isHeadOfLoop(stmtIndex, loops, cfg) || isEndOfLoop(stmtIndex, loops)) { NestedPathType.Repetition - } else if (isCondWithoutElse(stmtIndex, cfg)) { + } else if (isCondWithoutElse(stmtIndex, cfg, processedIfs)) { NestedPathType.CondWithoutAlternative } else { NestedPathType.CondWithAlternative @@ -1096,8 +1116,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // Use a while instead of a foreach loop in order to stop when the parent was found while (parent.isEmpty && nextPossibleParentIndex < childrenOf.length) { val possibleParent = childrenOf(nextPossibleParentIndex) - // The parent element must fully contain the child - if (nextCS._1 > possibleParent._1._1 && nextCS._1 < possibleParent._1._2) { + // The parent element must contain the child + if (nextCS._1 > possibleParent._1._1 && nextCS._1 <= possibleParent._1._2) { parent = Some(nextPossibleParentIndex) } else { nextPossibleParentIndex += 1 From 68d5309ce9978b8f11b7ee6787f6d1e080cfe97c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 10:36:21 +0100 Subject: [PATCH 260/316] Made the path finding procedure more robust. --- .../preprocessing/AbstractPathFinder.scala | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index f270490c8c..88a68e39ba 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -418,46 +418,49 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val stack = mutable.Stack[Int](start) while (stack.nonEmpty) { val popped = stack.pop() - var nextBlock = cfg.bb(popped).successors.map { - case bb: BasicBlock ⇒ bb.startPC - // Handle Catch Nodes? - case _ ⇒ -1 - }.max + if (popped <= end) { + var nextBlock = cfg.bb(popped).successors.map { + case bb: BasicBlock ⇒ bb.startPC + // Handle Catch Nodes? + case _ ⇒ -1 + }.max - if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { - nextBlock = popped + 1 - while (!cfg.code.instructions(nextBlock).isInstanceOf[If[V]]) { - nextBlock += 1 + if (pathType == NestedPathType.CondWithAlternative && nextBlock > end) { + nextBlock = popped + 1 + while (nextBlock < cfg.code.instructions.length - 1 && + !cfg.code.instructions(nextBlock).isInstanceOf[If[V]]) { + nextBlock += 1 + } } - } - var containsIf = false - for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { - if (cfg.code.instructions(i).isInstanceOf[If[V]]) { - containsIf = true + var containsIf = false + for (i ← cfg.bb(nextBlock).startPC.to(cfg.bb(nextBlock).endPC)) { + if (cfg.code.instructions(i).isInstanceOf[If[V]]) { + containsIf = true + } } - } - if (containsIf) { - startEndPairs.append((popped, nextBlock - 1)) - stack.push(nextBlock) - } else { - if (popped <= end) { - endSite = nextBlock - 1 - if (endSite == start) { - endSite = end - } // The following is necessary to not exceed bounds (might be the case within a - // try block for example) - else if (endSite > end) { - endSite = end + if (containsIf) { + startEndPairs.append((popped, nextBlock - 1)) + stack.push(nextBlock) + } else { + if (popped <= end) { + endSite = nextBlock - 1 + if (endSite == start) { + endSite = end + } // The following is necessary to not exceed bounds (might be the case + // within a try block for example) + else if (endSite > end) { + endSite = end + } + startEndPairs.append((popped, endSite)) } - startEndPairs.append((popped, endSite)) } } } // Append the "else" branch (if present) - if (pathType == NestedPathType.CondWithAlternative) { + if (pathType == NestedPathType.CondWithAlternative && startEndPairs.last._2 + 1 <= end) { startEndPairs.append((startEndPairs.last._2 + 1, end)) } @@ -470,7 +473,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) } } - (Path(List(NestedPathElement(subPaths, Some(pathType)))), startEndPairs.toList) + + val pathTypeToUse = if (pathType == NestedPathType.CondWithAlternative && + startEndPairs.length == 1) NestedPathType.CondWithoutAlternative else pathType + + (Path(List(NestedPathElement(subPaths, Some(pathTypeToUse)))), startEndPairs.toList) } /** From bf7e6b13ad40cbe38518a6719f97e1c62ac5016c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 14:27:04 +0100 Subject: [PATCH 261/316] Refined the handling of the TACMade the path finding procedure more robust. --- .../preprocessing/AbstractPathFinder.scala | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 88a68e39ba..7184011a84 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -468,10 +468,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { startEndPairs.foreach { case (startSubpath, endSubpath) ⇒ val subpathElements = ListBuffer[SubPath]() - subPaths.append(NestedPathElement(subpathElements, None)) if (fill) { subpathElements.appendAll(startSubpath.to(endSubpath).map(FlatPathElement)) } + if (!fill || subpathElements.nonEmpty) + subPaths.append(NestedPathElement(subpathElements, None)) } val pathTypeToUse = if (pathType == NestedPathType.CondWithAlternative && @@ -1200,9 +1201,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (isRepElement) { npe.element.append(nextEle) } else { - npe.element(insertIndex).asInstanceOf[NestedPathElement].element.append( - nextEle - ) + if (insertIndex < npe.element.length) { + npe.element(insertIndex).asInstanceOf[NestedPathElement].element.append( + nextEle + ) + } } lastInsertedIndex = nextEle match { @@ -1211,7 +1214,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // Compiler wants it but should never be the case! case _ ⇒ -1 } - if (lastInsertedIndex >= startEndPairs(insertIndex)._2) { + if (insertIndex < startEndPairs.length && + lastInsertedIndex >= startEndPairs(insertIndex)._2) { insertIndex += 1 } } @@ -1236,7 +1240,15 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } } } - finalPath.append(subpath.elements.head) + // Make sure to have no empty lists + val subPathNpe = subpath.elements.head.asInstanceOf[NestedPathElement] + val subPathToAdd = NestedPathElement( + subPathNpe.element.filter { + case npe: NestedPathElement ⇒ npe.element.nonEmpty + case _ ⇒ true + }, subPathNpe.elementType + ) + finalPath.append(subPathToAdd) } indexLastCSEnd = nextTopEle.hierarchy.head._1.get._2 + 1 } From f9eddb6c97bf63d3160525393699c1734894a68b Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 14:27:24 +0100 Subject: [PATCH 262/316] Fixed a TODO regarding housekeeping. --- .../string_analysis/InterproceduralStringAnalysis.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index eaad0ea075..e7b7fef3cd 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -329,8 +329,12 @@ class InterproceduralStringAnalysis( val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) val result = Result(resultEntity, p) state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result - // TODO: Is that correct? (rather remove only the function from the list) - state.entity2Function.remove(resultEntity) + // Housekeeping + val index = state.entity2Function(resultEntity).indexOf(f) + state.entity2Function(resultEntity).remove(index) + if (state.entity2Function(resultEntity).isEmpty) { + state.entity2Function.remove(resultEntity) + } } // Continue only after all necessary function parameters are evaluated if (state.entity2Function.nonEmpty) { From 0e0e81d5c9846282f01456b5aa5179ffe8150d3c Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 17:09:58 +0100 Subject: [PATCH 263/316] Do not compare by reference. --- .../string_analysis/InterproceduralStringAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index e7b7fef3cd..88d94f8ed2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -267,7 +267,7 @@ class InterproceduralStringAnalysis( private def continuation( state: InterproceduralComputationState )(eps: SomeEPS): ProperPropertyComputationResult = { - state.dependees = state.dependees.filter(_.e ne eps.e) + state.dependees = state.dependees.filter(_.e != eps.e) eps.pk match { case TACAI.key ⇒ eps match { case FinalP(tac: TACAI) ⇒ From 5f20b822bf61fe641cfb2020dc723703fc1116fd Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 19:40:38 +0100 Subject: [PATCH 264/316] Added support for correctly handling intermediate results. --- .../InterproceduralTestMethods.java | 42 +++--- .../StringConstancyInformation.scala | 10 ++ .../InterproceduralComputationState.scala | 23 +++ .../InterproceduralStringAnalysis.scala | 22 ++- ...InterproceduralInterpretationHandler.scala | 136 ++++++++++++++---- 5 files changed, 185 insertions(+), 48 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 3ba9106a70..864a2c11f5 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -528,27 +528,27 @@ public void fieldInitByConstructorParameter() { analyzeString(new StringBuilder().append(secretNumber).toString()); } -// @StringDefinitionsCollection( -// value = "a case where no callers information need to be computed", -// stringDefinitions = { -// @StringDefinitions( -// expectedLevel = CONSTANT, -// expectedStrings = "value" -// ) -// }) -// public String cyclicDependencyTest(String s) { -// String value = getProperty(s); -// analyzeString(value); -// return value; -// } -// -// private String getProperty(String name) { -// if (name == null) { -// return cyclicDependencyTest("default"); -// } else { -// return "value"; -// } -// } + @StringDefinitionsCollection( + value = "a case where no callers information need to be computed", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" // Should be rather (\\w|value) + ) + }) + public String cyclicDependencyTest(String s) { + String value = getProperty(s); + analyzeString(value); + return value; + } + + private String getProperty(String name) { + if (name == null) { + return cyclicDependencyTest("default"); + } else { + return "value"; + } + } private String getRuntimeClassName() { return "java.lang.Runtime"; diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index d75e032fe0..61c77afdf6 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -93,6 +93,16 @@ object StringConstancyInformation { } } + /** + * @return Returns a [[StringConstancyInformation]] element that corresponds to the lower bound + * from a lattice-based point of view. + */ + def lb: StringConstancyInformation = StringConstancyInformation( + StringConstancyLevel.DYNAMIC, + StringConstancyType.APPEND, + StringConstancyInformation.UnknownWordSymbol + ) + /** * @return Returns the / a neutral [[StringConstancyInformation]] element, i.e., an element for * which [[StringConstancyInformation.isTheNeutralElement]] is `true`. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index cb637c818d..6b62bfb73b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -70,6 +70,15 @@ case class InterproceduralComputationState(entity: P) { */ val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() + /** + * A mapping from a value / index of a FlatPathElement to StringConstancyInformation which is + * not yet final. For [[fpe2sci]] a list of [[StringConstancyInformation]] is necessary to + * compute (intermediate) results which might not be done in a single analysis step. For the + * interims, a single [[StringConstancyInformation]] element is sufficient, as it captures the + * results from [[fpe2sci]]. + */ + val interimFpe2sci: mutable.Map[Int, StringConstancyInformation] = mutable.Map() + /** * An analysis may depend on the evaluation of its parameters. This number indicates how many * of such dependencies are still to be computed. @@ -159,6 +168,20 @@ case class InterproceduralComputationState(entity: P) { } } + /** + * Sets a value for the [[interimFpe2sci]] map. + */ + def setInterimFpe2Sci(defSite: Int, sci: StringConstancyInformation): Unit = + interimFpe2sci(defSite) = sci + + /** + * Sets a result for the [[interimFpe2sci]] map. `r` is required to be a final result! + */ + def setInterimFpe2Sci(defSite: Int, r: Result): Unit = appendToFpe2Sci( + defSite, + StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation, + ) + /** * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 88d94f8ed2..cfc4630b97 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -359,7 +359,27 @@ class InterproceduralStringAnalysis( } case InterimLUBP(lb: StringConstancyProperty, ub: StringConstancyProperty) ⇒ state.dependees = eps :: state.dependees - getInterimResult(state, lb, ub) + + // If a new upper bound value is present, recompute a new interim result + var recomputeInterim = false + state.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i ⇒ + if (!state.interimFpe2sci.contains(i) || + ub.stringConstancyInformation != state.interimFpe2sci(i)) { + state.setInterimFpe2Sci(i, ub.stringConstancyInformation) + recomputeInterim = true + } + } + // Either set a new interim result or use the old one if nothing has changed + val ubForInterim = if (recomputeInterim) { + val fpe2SciMapping = state.interimFpe2sci.map { + case (key, value) ⇒ key → ListBuffer(value) + } + StringConstancyProperty(new PathTransformer(state.iHandler).pathToStringTree( + state.computedLeanPath, fpe2SciMapping + ).reduce(true)) + } else ub + + getInterimResult(state, lb, ubForInterim) case _ ⇒ state.dependees = eps :: state.dependees getInterimResult(state) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index f4f6896ac7..5d82f54301 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -80,10 +80,14 @@ class InterproceduralInterpretationHandler( // implicit parameter for "this" and for exceptions thrown outside the current function) if (defSite < 0 && (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset)) { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) return Result(e, StringConstancyProperty.lb) } else if (defSite < 0) { - return Result(e, StringConstancyProperty(getParam(params, defSite))) + val sci = getParam(params, defSite) + state.setInterimFpe2Sci(defSite, sci) + return Result(e, StringConstancyProperty(sci)) } else if (processedDefSites.contains(defSite)) { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) return Result(e, StringConstancyProperty.getNeutralElement) } // Note that def sites referring to constant expressions will be deleted further down @@ -92,23 +96,35 @@ class InterproceduralInterpretationHandler( val callees = state.callees stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ - val result = new StringConstInterpreter(cfg, this).interpret(expr, defSite) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + val result = new StringConstInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) processedDefSites.remove(defSite) result case Assignment(_, _, expr: IntConst) ⇒ - val result = new IntegerValueInterpreter(cfg, this).interpret(expr, defSite) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + val result = new IntegerValueInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) processedDefSites.remove(defSite) result case Assignment(_, _, expr: FloatConst) ⇒ - val result = new FloatValueInterpreter(cfg, this).interpret(expr, defSite) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + val result = new FloatValueInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) processedDefSites.remove(defSite) result case Assignment(_, _, expr: DoubleConst) ⇒ - val result = new DoubleValueInterpreter(cfg, this).interpret(expr, defSite) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + val result = new DoubleValueInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) processedDefSites.remove(defSite) result case Assignment(_, _, expr: ArrayLoad[V]) ⇒ @@ -116,69 +132,129 @@ class InterproceduralInterpretationHandler( cfg, this, state, params ).interpret(expr, defSite) if (!r.isInstanceOf[Result]) { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) processedDefSites.remove(defSite) } r case Assignment(_, _, expr: New) ⇒ - val result = new NewInterpreter(cfg, this).interpret(expr, defSite) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + val result = new NewInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) result case Assignment(_, _, expr: GetStatic) ⇒ - new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) + val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.setInterimFpe2Sci(defSite, result) + result case ExprStmt(_, expr: GetStatic) ⇒ - new InterproceduralGetStaticInterpreter(cfg, this).interpret(expr, defSite) + val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.setInterimFpe2Sci(defSite, result) + result case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + val isFinalResult = r.isInstanceOf[Result] + if (!isFinalResult || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } + + if (isFinalResult) { + state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + } else { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + } + r case Assignment(_, _, expr: BinaryExpr[V]) ⇒ val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) + state.setInterimFpe2Sci(defSite, result.asInstanceOf[Result]) state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) result case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + val isFinalResult = r.isInstanceOf[Result] + if (!isFinalResult || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } + + if (isFinalResult) { + state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + } else { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + } + r case Assignment(_, _, expr: GetField[V]) ⇒ val r = new InterproceduralFieldInterpreter( state, this, ps, fieldAccessInformation, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result]) { + val isFinalResult = r.isInstanceOf[Result] + if (!isFinalResult) { processedDefSites.remove(defSite) } + + if (isFinalResult) { + state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + } else { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + } + r case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + val isFinalResult = r.isInstanceOf[Result] + if (!isFinalResult || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } + + if (isFinalResult) { + state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + } else { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + } + r case vmc: VirtualMethodCall[V] ⇒ - new InterproceduralVirtualMethodCallInterpreter( + val r = new InterproceduralVirtualMethodCallInterpreter( cfg, this, callees ).interpret(vmc, defSite) + + val isFinalResult = r.isInstanceOf[Result] + if (isFinalResult) { + state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + } else { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + } + + r case nvmc: NonVirtualMethodCall[V] ⇒ - val result = new InterproceduralNonVirtualMethodCallInterpreter( + val r = new InterproceduralNonVirtualMethodCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(nvmc, defSite) - result match { - case r: Result ⇒ state.appendResultToFpe2Sci(defSite, r) - case _ ⇒ processedDefSites.remove(defSite) + r match { + case r: Result ⇒ + state.setInterimFpe2Sci(defSite, r) + state.appendResultToFpe2Sci(defSite, r) + case _ ⇒ + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + processedDefSites.remove(defSite) } - result - case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) + r + case _ ⇒ + state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) + Result(e, StringConstancyProperty.getNeutralElement) } } @@ -197,7 +273,8 @@ class InterproceduralInterpretationHandler( // call was not fully prepared before (no final result available) or 2) the preparation is // now done (methodPrep2defSite makes sure we have the TAC ready for a method required by // this virtual function call). - if (!r.isInstanceOf[Result] && !state.isVFCFullyPrepared.contains(expr)) { + val isFinalResult = r.isInstanceOf[Result] + if (!isFinalResult && !state.isVFCFullyPrepared.contains(expr)) { state.isVFCFullyPrepared(expr) = false } else if (state.isVFCFullyPrepared.contains(expr) && state.methodPrep2defSite.isEmpty) { state.isVFCFullyPrepared(expr) = true @@ -212,11 +289,18 @@ class InterproceduralInterpretationHandler( // prepared in the same way as other calls are as toString does not take any arguments that // might need to be prepared (however, toString needs a finalization procedure) if (expr.name == "toString" && - (state.nonFinalFunctionArgs.contains(expr) || !r.isInstanceOf[Result])) { + (state.nonFinalFunctionArgs.contains(expr) || !isFinalResult)) { processedDefSites.remove(defSite) } else if (state.nonFinalFunctionArgs.contains(expr) || !isPrepDone) { processedDefSites.remove(defSite) } + + if (isFinalResult) { + state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + } else { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + } + r } From 72ac05be94546c1b0d1d6d1054e66be7c8b326d7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 10 Mar 2019 22:07:37 +0100 Subject: [PATCH 265/316] Avoided code duplication and cleaned the code. --- ...InterproceduralInterpretationHandler.scala | 342 ++++++++++-------- 1 file changed, 187 insertions(+), 155 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 5d82f54301..6055c98b85 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -8,6 +8,7 @@ import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.FieldAccessInformation +import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.ai.ImmediateVMExceptionsOriginOffset @@ -43,6 +44,7 @@ import org.opalj.tac.GetStatic import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.GetFieldFinalizer +import org.opalj.tac.SimpleValueConst /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -95,163 +97,27 @@ class InterproceduralInterpretationHandler( val callees = state.callees stmts(defSite) match { - case Assignment(_, _, expr: StringConst) ⇒ - val result = new StringConstInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) - processedDefSites.remove(defSite) - result - case Assignment(_, _, expr: IntConst) ⇒ - val result = new IntegerValueInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) - processedDefSites.remove(defSite) - result - case Assignment(_, _, expr: FloatConst) ⇒ - val result = new FloatValueInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) - processedDefSites.remove(defSite) - result - case Assignment(_, _, expr: DoubleConst) ⇒ - val result = new DoubleValueInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) - processedDefSites.remove(defSite) - result + case Assignment(_, _, expr: StringConst) ⇒ processConstExpr(expr, defSite) + case Assignment(_, _, expr: IntConst) ⇒ processConstExpr(expr, defSite) + case Assignment(_, _, expr: FloatConst) ⇒ processConstExpr(expr, defSite) + case Assignment(_, _, expr: DoubleConst) ⇒ processConstExpr(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ - val r = new ArrayPreparationInterpreter( - cfg, this, state, params - ).interpret(expr, defSite) - if (!r.isInstanceOf[Result]) { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - processedDefSites.remove(defSite) - } - r - case Assignment(_, _, expr: New) ⇒ - val result = new NewInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) - result - case Assignment(_, _, expr: GetStatic) ⇒ - val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.setInterimFpe2Sci(defSite, result) - result - case ExprStmt(_, expr: GetStatic) ⇒ - val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( - expr, defSite - ).asInstanceOf[Result] - state.setInterimFpe2Sci(defSite, result) - result + processArrayLoad(expr, defSite, params) + case Assignment(_, _, expr: New) ⇒ processNew(expr, defSite) + case Assignment(_, _, expr: GetStatic) ⇒ processGetStatic(expr, defSite) + case ExprStmt(_, expr: GetStatic) ⇒ processGetStatic(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ - val r = new InterproceduralStaticFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods, c - ).interpret(expr, defSite) - val isFinalResult = r.isInstanceOf[Result] - if (!isFinalResult || state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(defSite) - } - - if (isFinalResult) { - state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) - } else { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - } - - r - case Assignment(_, _, expr: BinaryExpr[V]) ⇒ - val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) - state.setInterimFpe2Sci(defSite, result.asInstanceOf[Result]) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) - result + processStaticFunctionCall(expr, defSite) + case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ processStaticFunctionCall(expr, defSite) + case Assignment(_, _, expr: BinaryExpr[V]) ⇒ processBinaryExpr(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ - val r = new InterproceduralNonVirtualFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods, c - ).interpret(expr, defSite) - val isFinalResult = r.isInstanceOf[Result] - if (!isFinalResult || state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(defSite) - } - - if (isFinalResult) { - state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) - } else { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - } - - r - case Assignment(_, _, expr: GetField[V]) ⇒ - val r = new InterproceduralFieldInterpreter( - state, this, ps, fieldAccessInformation, c - ).interpret(expr, defSite) - val isFinalResult = r.isInstanceOf[Result] - if (!isFinalResult) { - processedDefSites.remove(defSite) - } - - if (isFinalResult) { - state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) - } else { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - } - - r - case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ - val r = new InterproceduralStaticFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods, c - ).interpret(expr, defSite) - val isFinalResult = r.isInstanceOf[Result] - if (!isFinalResult || state.nonFinalFunctionArgs.contains(expr)) { - processedDefSites.remove(defSite) - } - - if (isFinalResult) { - state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) - } else { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - } - - r + processNonVirtualFunctionCall(expr, defSite) + case Assignment(_, _, expr: GetField[V]) ⇒ processGetField(expr, defSite) case vmc: VirtualMethodCall[V] ⇒ - val r = new InterproceduralVirtualMethodCallInterpreter( - cfg, this, callees - ).interpret(vmc, defSite) - - val isFinalResult = r.isInstanceOf[Result] - if (isFinalResult) { - state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) - } else { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - } - - r - case nvmc: NonVirtualMethodCall[V] ⇒ - val r = new InterproceduralNonVirtualMethodCallInterpreter( - cfg, this, ps, state, declaredMethods, c - ).interpret(nvmc, defSite) - r match { - case r: Result ⇒ - state.setInterimFpe2Sci(defSite, r) - state.appendResultToFpe2Sci(defSite, r) - case _ ⇒ - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - processedDefSites.remove(defSite) - } - r + processVirtualMethodCall(vmc, defSite, callees) + case nvmc: NonVirtualMethodCall[V] ⇒ processNonVirtualMethodCall(nvmc, defSite) case _ ⇒ state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) Result(e, StringConstancyProperty.getNeutralElement) @@ -259,7 +125,68 @@ class InterproceduralInterpretationHandler( } /** - * Helper function for interpreting [[VirtualFunctionCall]]s. + * Helper / utility function for processing [[StringConst]], [[IntConst]], [[FloatConst]], and + * [[DoubleConst]]. + */ + private def processConstExpr( + constExpr: SimpleValueConst, defSite: Int + ): ProperPropertyComputationResult = { + val ppcr = constExpr match { + case ic: IntConst ⇒ new IntegerValueInterpreter(cfg, this).interpret(ic, defSite) + case fc: FloatConst ⇒ new FloatValueInterpreter(cfg, this).interpret(fc, defSite) + case dc: DoubleConst ⇒ new DoubleValueInterpreter(cfg, this).interpret(dc, defSite) + case sc ⇒ new StringConstInterpreter(cfg, this).interpret( + sc.asInstanceOf[StringConst], defSite + ) + } + val result = ppcr.asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) + processedDefSites.remove(defSite) + result + } + + /** + * Helper / utility function for processing [[ArrayLoad]]s. + */ + private def processArrayLoad( + expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] + ): ProperPropertyComputationResult = { + val r = new ArrayPreparationInterpreter( + cfg, this, state, params + ).interpret(expr, defSite) + if (!r.isInstanceOf[Result]) { + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + processedDefSites.remove(defSite) + } + r + } + + /** + * Helper / utility function for processing [[New]] expressions. + */ + private def processNew(expr: New, defSite: Int): ProperPropertyComputationResult = { + val result = new NewInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.appendResultToFpe2Sci(defSite, result) + state.setInterimFpe2Sci(defSite, result) + result + } + + /** + * Helper / utility function for processing [[GetStatic]]s. + */ + private def processGetStatic(expr: GetStatic, defSite: Int): ProperPropertyComputationResult = { + val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( + expr, defSite + ).asInstanceOf[Result] + state.setInterimFpe2Sci(defSite, result) + result + } + + /** + * Helper / utility function for interpreting [[VirtualFunctionCall]]s. */ private def processVFC( expr: VirtualFunctionCall[V], @@ -295,13 +222,118 @@ class InterproceduralInterpretationHandler( processedDefSites.remove(defSite) } + doInterimResultHandling(r, defSite) + r + } + + /** + * Helper / utility function for processing [[StaticFunctionCall]]s. + */ + private def processStaticFunctionCall( + expr: StaticFunctionCall[V], defSite: Int + ): ProperPropertyComputationResult = { + val r = new InterproceduralStaticFunctionCallInterpreter( + cfg, this, ps, state, declaredMethods, c + ).interpret(expr, defSite) + if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + processedDefSites.remove(defSite) + } + doInterimResultHandling(r, defSite) + + r + } + + /** + * Helper / utility function for processing [[BinaryExpr]]s. + */ + private def processBinaryExpr( + expr: BinaryExpr[V], defSite: Int + ): ProperPropertyComputationResult = { + val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) + state.setInterimFpe2Sci(defSite, result.asInstanceOf[Result]) + state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + result + } + + /** + * Helper / utility function for processing [[GetField]]s. + */ + private def processGetField( + expr: GetField[V], defSite: Int + ): ProperPropertyComputationResult = { + val r = new InterproceduralFieldInterpreter( + state, this, ps, fieldAccessInformation, c + ).interpret(expr, defSite) + if (!r.isInstanceOf[Result]) { + processedDefSites.remove(defSite) + } + doInterimResultHandling(r, defSite) + r + } + + /** + * Helper / utility function for processing [[NonVirtualMethodCall]]s. + */ + private def processNonVirtualFunctionCall( + expr: NonVirtualFunctionCall[V], defSite: Int + ): ProperPropertyComputationResult = { + val r = new InterproceduralNonVirtualFunctionCallInterpreter( + cfg, this, ps, state, declaredMethods, c + ).interpret(expr, defSite) + if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + processedDefSites.remove(defSite) + } + doInterimResultHandling(r, defSite) + r + } + + /** + * Helper / utility function for processing [[VirtualMethodCall]]s. + */ + def processVirtualMethodCall( + expr: VirtualMethodCall[V], defSite: Int, callees: Callees + ): ProperPropertyComputationResult = { + val r = new InterproceduralVirtualMethodCallInterpreter( + cfg, this, callees + ).interpret(expr, defSite) + doInterimResultHandling(r, defSite) + r + } + + /** + * Helper / utility function for processing [[NonVirtualMethodCall]]s. + */ + private def processNonVirtualMethodCall( + nvmc: NonVirtualMethodCall[V], defSite: Int + ): ProperPropertyComputationResult = { + val r = new InterproceduralNonVirtualMethodCallInterpreter( + cfg, this, ps, state, declaredMethods, c + ).interpret(nvmc, defSite) + r match { + case r: Result ⇒ + state.setInterimFpe2Sci(defSite, r) + state.appendResultToFpe2Sci(defSite, r) + case _ ⇒ + state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + processedDefSites.remove(defSite) + } + r + } + + /** + * This function takes a result, which can be final or not, as well as a definition site. This + * function handles the steps necessary to provide information for computing intermediate + * results. + */ + private def doInterimResultHandling( + result: ProperPropertyComputationResult, defSite: Int + ): Unit = { + val isFinalResult = result.isInstanceOf[Result] if (isFinalResult) { - state.setInterimFpe2Sci(defSite, r.asInstanceOf[Result]) + state.setInterimFpe2Sci(defSite, result.asInstanceOf[Result]) } else { state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) } - - r } /** From 0a37642b166a5a2101493f5c202280759265cfb6 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 07:26:37 +0100 Subject: [PATCH 266/316] Added support for non-virtual functions which return more than one value. --- .../InterproceduralTestMethods.java | 27 +++++++++++ ...ralNonVirtualFunctionCallInterpreter.scala | 45 +++++++++++-------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 864a2c11f5..97b29391c4 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -550,6 +550,29 @@ private String getProperty(String name) { } } + @StringDefinitionsCollection( + value = "a case where a non virtual function has multiple return values", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(One|val|java.lang.Object)" + ) + }) + public void severalReturnValuesTest1() { + analyzeString(severalReturnValuesFunction("val", 42)); + } + + /** + * Belongs to severalReturnValuesTest1. + */ + private String severalReturnValuesFunction(String s, int i) { + switch (i) { + case 0: return getObjectClassName(); + case 1: return "One"; + default: return s; + } + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } @@ -574,4 +597,8 @@ private String addQuestionMark(String s) { return s + "?"; } + private String getObjectClassName() { + return "java.lang.Object"; + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 2332e29415..faf10fcd1a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -59,25 +59,34 @@ class InterproceduralNonVirtualFunctionCallInterpreter( val (_, tac) = getTACAI(ps, m, state) if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) - // TAC available => Get return UVar and start the string analysis - val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get - val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar - val entity = (uvar, m) + // TAC available => Get return UVars and start the string analysis + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + if (returns.isEmpty) { + // A function without returns, e.g., because it is guaranteed to throw an exception, + // is approximated with the lower bound + Result(instr, StringConstancyProperty.lb) + } else { + val results = returns.map { ret ⇒ + val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar + val entity = (uvar, m) - val eps = ps(entity, StringConstancyProperty.key) - eps match { - case FinalEP(e, p) ⇒ - Result(e, p) - case _ ⇒ - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(uvar, defSite) - InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case FinalEP(e, p) ⇒ + Result(e, p) + case _ ⇒ + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(uvar, defSite) + InterimResult( + state.entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + state.dependees, + c + ) + } + } + results.find(!_.isInstanceOf[Result]).getOrElse(results.head) } } else { state.appendToMethodPrep2defSite(m, defSite) From f9d1ffc5e4672b85d3a4a163fe5de4e025a2ab7f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 07:57:15 +0100 Subject: [PATCH 267/316] Added support for static functions which return more than one value. --- .../InterproceduralTestMethods.java | 29 ++++++++ ...ceduralStaticFunctionCallInterpreter.scala | 69 +++++++++---------- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 97b29391c4..bab6d4e4e3 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -573,6 +573,31 @@ private String severalReturnValuesFunction(String s, int i) { } } + @StringDefinitionsCollection( + value = "a case where a non static function has multiple return values", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(that's odd|my.helper.Class)" + ) + }) + public void severalReturnValuesTest2() { + analyzeString(severalReturnValuesStaticFunction(42)); + } + + /** + * Belongs to severalReturnValuesTest2. + */ + private static String severalReturnValuesStaticFunction(int i) { + // The ternary operator would create only a single "return" statement which is not what we + // want here + if (i % 2 != 0) { + return "that's odd"; + } else { + return getHelperClass(); + } + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } @@ -601,4 +626,8 @@ private String getObjectClassName() { return "java.lang.Object"; } + private static String getHelperClass() { + return "my.helper.Class"; + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 00320e82e6..ab2096725c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -10,19 +10,14 @@ import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.br.Method import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.ReturnValue -import org.opalj.tac.fpcf.analyses.string_analysis import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis -import org.opalj.tac.TACMethodParameter -import org.opalj.tac.TACode -import org.opalj.tac.fpcf.analyses.string_analysis.P +import org.opalj.tac.ReturnValue /** * The `InterproceduralStaticFunctionCallInterpreter` is responsible for processing @@ -45,15 +40,6 @@ class InterproceduralStaticFunctionCallInterpreter( override type T = StaticFunctionCall[V] - private def extractReturnsFromFunction( - tac: TACode[TACMethodParameter, string_analysis.V], - m: Method - ): P = { - val ret = tac.stmts.find(_.isInstanceOf[ReturnValue[V]]).get - val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar - (uvar, m) - } - /** * This function always returns a list with a single element consisting of * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]], @@ -104,10 +90,13 @@ class InterproceduralStaticFunctionCallInterpreter( val nonFinalResults = getNonFinalParameters(params) if (nonFinalResults.nonEmpty) { if (tac.isDefined) { - val e = extractReturnsFromFunction(tac.get, m) - val eps = ps(e, StringConstancyProperty.key) - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(e._1, defSite) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + returns.foreach { ret ⇒ + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) + val eps = ps(entity, StringConstancyProperty.key) + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(entity._1, defSite) + } } state.nonFinalFunctionArgs(instr) = params state.appendToMethodPrep2defSite(m, defSite) @@ -120,23 +109,33 @@ class InterproceduralStaticFunctionCallInterpreter( if (tac.isDefined) { state.removeFromMethodPrep2defSite(m, defSite) // TAC available => Get return UVar and start the string analysis - val entity = extractReturnsFromFunction(tac.get, m) - InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + if (returns.isEmpty) { + // A function without returns, e.g., because it is guaranteed to throw an exception, + // is approximated with the lower bound + Result(instr, StringConstancyProperty.lb) + } else { + val results = returns.map { ret ⇒ + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) + InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) - val eps = ps(entity, StringConstancyProperty.key) - eps match { - case FinalEP(e, p) ⇒ - Result(e, p) - case _ ⇒ - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) - InterimResult( - entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - List(), - c - ) + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case FinalEP(e, p) ⇒ + Result(e, p) + case _ ⇒ + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(entity._1, defSite) + InterimResult( + entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + List(), + c + ) + } + } + results.find(!_.isInstanceOf[Result]).getOrElse(results.head) } } else { // No TAC => Register dependee and continue From b98ed9ea44fd52b8b04f0c5d61ef3d99cea0fefa Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 08:28:14 +0100 Subject: [PATCH 268/316] Added test cases where functions have no return values. --- .../InterproceduralTestMethods.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index bab6d4e4e3..50220a21a7 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -6,6 +6,7 @@ import org.opalj.fpcf.fixtures.string_analysis.hierarchies.HelloGreeting; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import javax.management.remote.rmi.RMIServer; import java.io.File; @@ -598,6 +599,44 @@ private static String severalReturnValuesStaticFunction(int i) { } } + @StringDefinitionsCollection( + value = "a case where a non-virtual function has no return values at all", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" + ) + }) + public void functionWithNoReturnValueTest1() { + analyzeString(noReturnFunction1()); + } + + /** + * Belongs to functionWithNoReturnValueTest1. + */ + public String noReturnFunction1() { + throw new NotImplementedException(); + } + + @StringDefinitionsCollection( + value = "a case where a static function has no return values at all", + stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" + ) + }) + public void functionWithNoReturnValueTest2() { + analyzeString(noReturnFunction2()); + } + + /** + * Belongs to functionWithNoReturnValueTest2. + */ + public static String noReturnFunction2() { + throw new NotImplementedException(); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } From b2129f8f1b794014a249d65217896835458bbb55 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 08:35:06 +0100 Subject: [PATCH 269/316] Virtual function with several return values are now supported as well. --- ...alFunctionCallPreparationInterpreter.scala | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 8b7b1970b3..d98390e16c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -150,34 +150,33 @@ class VirtualFunctionCallPreparationInterpreter( val (_, tac) = getTACAI(ps, nextMethod, state) if (tac.isDefined) { state.methodPrep2defSite.remove(nextMethod) - // It might be that a function has no return value, e. g., in case it is guaranteed - // to throw an exception (see, e.g., - // com.sun.org.apache.xpath.internal.objects.XRTreeFragSelectWrapper#str) - if (!tac.get.stmts.exists(_.isInstanceOf[ReturnValue[V]])) { + val returns = tac.get.stmts.filter(_.isInstanceOf[ReturnValue[V]]) + if (returns.isEmpty) { + // It might be that a function has no return value, e. g., in case it is + // guaranteed to throw an exception Result(instr, StringConstancyProperty.lb) } else { - // TAC and return available => Get return UVar and start the string analysis - val ret = tac.get.stmts.find(_.isInstanceOf[ReturnValue[V]]).get - val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar - val entity = (uvar, nextMethod) - - InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) - val eps = ps(entity, StringConstancyProperty.key) - eps match { - case r: Result ⇒ - state.appendResultToFpe2Sci(defSite, r) - r - case _ ⇒ - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(uvar, defSite) - InterimResult( - entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - List(), - c - ) + val results = returns.map { ret ⇒ + val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, nextMethod) + InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) + val eps = ps(entity, StringConstancyProperty.key) + eps match { + case r: Result ⇒ + state.appendResultToFpe2Sci(defSite, r) + r + case _ ⇒ + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(entity._1, defSite) + InterimResult( + entity, + StringConstancyProperty.lb, + StringConstancyProperty.ub, + List(), + c + ) + } } + results.find(!_.isInstanceOf[Result]).getOrElse(results.head) } } else { state.appendToMethodPrep2defSite(nextMethod, defSite) From bce4bfbe3b2e5e175f107e4c75b944a2e844509f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 08:45:52 +0100 Subject: [PATCH 270/316] Merged two test cases into one. --- .../InterproceduralTestMethods.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 50220a21a7..96e4418066 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -600,8 +600,12 @@ private static String severalReturnValuesStaticFunction(int i) { } @StringDefinitionsCollection( - value = "a case where a non-virtual function has no return values at all", + value = "a case where a non-virtual and a static function have no return values at all", stringDefinitions = { + @StringDefinitions( + expectedLevel = DYNAMIC, + expectedStrings = "\\w" + ), @StringDefinitions( expectedLevel = DYNAMIC, expectedStrings = "\\w" @@ -609,6 +613,7 @@ private static String severalReturnValuesStaticFunction(int i) { }) public void functionWithNoReturnValueTest1() { analyzeString(noReturnFunction1()); + analyzeString(noReturnFunction2()); } /** @@ -618,20 +623,8 @@ public String noReturnFunction1() { throw new NotImplementedException(); } - @StringDefinitionsCollection( - value = "a case where a static function has no return values at all", - stringDefinitions = { - @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "\\w" - ) - }) - public void functionWithNoReturnValueTest2() { - analyzeString(noReturnFunction2()); - } - /** - * Belongs to functionWithNoReturnValueTest2. + * Belongs to functionWithNoReturnValueTest1. */ public static String noReturnFunction2() { throw new NotImplementedException(); From 36ecc666c2c4e87ae0c23878111fa8f2f60a5942 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 10:12:21 +0100 Subject: [PATCH 271/316] Added support for calls to String#valueOf. --- .../InterproceduralTestMethods.java | 22 ++++++++ ...InterproceduralInterpretationHandler.scala | 16 ++++-- ...ceduralStaticFunctionCallInterpreter.scala | 53 +++++++++++++++++++ .../StaticFunctionCallFinalizer.scala | 53 +++++++++++++++++++ 4 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/StaticFunctionCallFinalizer.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 96e4418066..d185a2bc1b 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -630,6 +630,28 @@ public static String noReturnFunction2() { throw new NotImplementedException(); } + @StringDefinitionsCollection( + value = "a test case which tests the interpretation of String#valueOf", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "c" + ), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "42.3" + ), + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "java.lang.Runtime" + ) + }) + public void valueOfTest() { + analyzeString(String.valueOf('c')); + analyzeString(String.valueOf((float) 42.3)); + analyzeString(String.valueOf(getRuntimeClassName())); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 6055c98b85..d762e7b41c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -45,6 +45,7 @@ import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.GetFieldFinalizer import org.opalj.tac.SimpleValueConst +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.StaticFunctionCallFinalizer /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -109,9 +110,10 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ - processStaticFunctionCall(expr, defSite) - case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ processStaticFunctionCall(expr, defSite) - case Assignment(_, _, expr: BinaryExpr[V]) ⇒ processBinaryExpr(expr, defSite) + processStaticFunctionCall(expr, defSite, params) + case ExprStmt(_, expr: StaticFunctionCall[V]) ⇒ + processStaticFunctionCall(expr, defSite, params) + case Assignment(_, _, expr: BinaryExpr[V]) ⇒ processBinaryExpr(expr, defSite) case Assignment(_, _, expr: NonVirtualFunctionCall[V]) ⇒ processNonVirtualFunctionCall(expr, defSite) case Assignment(_, _, expr: GetField[V]) ⇒ processGetField(expr, defSite) @@ -230,10 +232,10 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[StaticFunctionCall]]s. */ private def processStaticFunctionCall( - expr: StaticFunctionCall[V], defSite: Int + expr: StaticFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): ProperPropertyComputationResult = { val r = new InterproceduralStaticFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods, c + cfg, this, ps, state, params, declaredMethods, c ).interpret(expr, defSite) if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -370,6 +372,10 @@ class InterproceduralInterpretationHandler( GetFieldFinalizer(state).finalizeInterpretation(gf, defSite) case ExprStmt(_, gf: GetField[V]) ⇒ GetFieldFinalizer(state).finalizeInterpretation(gf, defSite) + case Assignment(_, _, sfc: StaticFunctionCall[V]) ⇒ + StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) + case ExprStmt(_, sfc: StaticFunctionCall[V]) ⇒ + StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case _ ⇒ state.appendToFpe2Sci( defSite, StringConstancyProperty.lb.stringConstancyInformation, reset = true ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index ab2096725c..6e5ff59986 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -1,6 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +import scala.util.Try + import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation @@ -10,6 +12,7 @@ import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.StaticFunctionCall import org.opalj.tac.Stmt import org.opalj.tac.TACStmts @@ -34,6 +37,7 @@ class InterproceduralStaticFunctionCallInterpreter( exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]], declaredMethods: DeclaredMethods, c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { @@ -51,6 +55,55 @@ class InterproceduralStaticFunctionCallInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + if (instr.declaringClass.fqn == "java/lang/String" && instr.name == "valueOf") { + processStringValueOf(instr) + } else { + processArbitraryCall(instr, defSite) + } + } + + /** + * A function for processing calls to [[String#valueOf]]. This function assumes that the passed + * `call` element is actually such a call. + * This function returns an intermediate results if one or more interpretations could not be + * finished. Otherwise, if all definition sites could be fully processed, this function + * returns an instance of [[Result]] which corresponds to the result of the interpretation of + * the parameter passed to the call. + */ + private def processStringValueOf( + call: StaticFunctionCall[V] + ): ProperPropertyComputationResult = { + val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds ⇒ + exprHandler.processDefSite(ds, params) + } + val interim = results.find(!_.isInstanceOf[Result]) + if (interim.isDefined) { + interim.get + } else { + // For char values, we need to do a conversion (as the returned results are integers) + val scis = if (call.descriptor.parameterType(0).toJava == "char") { + results.map { r ⇒ + val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + if (Try(sci.possibleStrings.toInt).isSuccess) { + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + } else { + sci + } + } + } else { + results.map(StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation) + } + val finalSci = StringConstancyInformation.reduceMultiple(scis) + Result(call, StringConstancyProperty(finalSci)) + } + } + + /** + * This function interprets an arbitrary static function call. + */ + private def processArbitraryCall( + instr: StaticFunctionCall[V], defSite: Int + ): ProperPropertyComputationResult = { val methods, _ = getMethodsForPC( instr.pc, ps, state.callees, declaredMethods ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/StaticFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/StaticFunctionCallFinalizer.scala new file mode 100644 index 0000000000..8afee79f3a --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/StaticFunctionCallFinalizer.scala @@ -0,0 +1,53 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer + +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.StaticFunctionCall + +/** + * @author Patrick Mell + */ +class StaticFunctionCallFinalizer( + state: InterproceduralComputationState +) extends AbstractFinalizer(state) { + + override type T = StaticFunctionCall[V] + + /** + * Finalizes [[StaticFunctionCall]]s. + *

    + * @inheritdoc + */ + override def finalizeInterpretation(instr: T, defSite: Int): Unit = { + val isValueOf = instr.declaringClass.fqn == "java/lang/String" && instr.name == "valueOf" + val toAppend = if (isValueOf) { + // For the finalization we do not need to consider between chars and non-chars as chars + // are only considered when they are char constants and thus a final result is already + // computed by InterproceduralStaticFunctionCallInterpreter (which is why this method + // will not be called for char parameters) + val defSites = instr.params.head.asVar.definedBy.toArray.sorted + defSites.foreach { ds ⇒ + if (!state.fpe2sci.contains(ds)) { + state.iHandler.finalizeDefSite(ds, state) + } + } + val scis = defSites.map { state.fpe2sci } + StringConstancyInformation.reduceMultiple(scis.flatten.toList) + } else { + StringConstancyProperty.lb.stringConstancyInformation + } + state.appendToFpe2Sci(defSite, toAppend, reset = true) + } + +} + +object StaticFunctionCallFinalizer { + + def apply( + state: InterproceduralComputationState + ): StaticFunctionCallFinalizer = new StaticFunctionCallFinalizer(state) + +} \ No newline at end of file From 1e8ee3fea68b937dfc62a919c6ffd14f5cb96af2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 10:16:18 +0100 Subject: [PATCH 272/316] Avoided code duplication. --- .../opalj/br/fpcf/properties/StringConstancyProperty.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index 87bc7b80bb..aee0fffd7e 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -82,10 +82,6 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta * @return Returns the lower bound from a lattice-point of view. */ def lb: StringConstancyProperty = - StringConstancyProperty(StringConstancyInformation( - StringConstancyLevel.DYNAMIC, - StringConstancyType.APPEND, - StringConstancyInformation.UnknownWordSymbol - )) + StringConstancyProperty(StringConstancyInformation.lb) } From effd2cbde717aab4021933de28cea28d210cd1e0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 15:50:59 +0100 Subject: [PATCH 273/316] Changed the interface of AbstractStringInterpreter#interpret to return an instance of EOptionP[Entity, Property] to avoid creating InterimResults. This lead to changes in many files. --- .../properties/StringConstancyProperty.scala | 9 -- .../InterproceduralComputationState.scala | 22 ---- .../InterproceduralStringAnalysis.scala | 43 ++++---- .../IntraproceduralStringAnalysis.scala | 4 +- .../AbstractStringInterpreter.scala | 38 ++++--- .../InterpretationHandler.scala | 6 +- .../common/BinaryExprInterpreter.scala | 9 +- .../common/DoubleValueInterpreter.scala | 9 +- .../common/FloatValueInterpreter.scala | 9 +- .../common/IntegerValueInterpreter.scala | 9 +- .../common/NewInterpreter.scala | 9 +- .../common/StringConstInterpreter.scala | 9 +- .../ArrayPreparationInterpreter.scala | 44 ++++---- .../InterproceduralFieldInterpreter.scala | 66 +++++------- .../InterproceduralGetStaticInterpreter.scala | 11 +- ...InterproceduralInterpretationHandler.scala | 96 +++++++++-------- ...ralNonVirtualFunctionCallInterpreter.scala | 37 +++---- ...duralNonVirtualMethodCallInterpreter.scala | 36 ++++--- ...ceduralStaticFunctionCallInterpreter.scala | 62 +++++------ ...oceduralVirtualMethodCallInterpreter.scala | 10 +- ...alFunctionCallPreparationInterpreter.scala | 100 +++++++++--------- .../IntraproceduralArrayInterpreter.scala | 15 ++- .../IntraproceduralFieldInterpreter.scala | 9 +- .../IntraproceduralGetStaticInterpreter.scala | 9 +- ...IntraproceduralInterpretationHandler.scala | 19 ++-- ...ralNonVirtualFunctionCallInterpreter.scala | 9 +- ...duralNonVirtualMethodCallInterpreter.scala | 13 +-- ...ceduralStaticFunctionCallInterpreter.scala | 9 +- ...eduralVirtualFunctionCallInterpreter.scala | 25 ++--- ...oceduralVirtualMethodCallInterpreter.scala | 9 +- .../preprocessing/PathTransformer.scala | 6 +- .../string_analysis/string_analysis.scala | 6 +- 32 files changed, 367 insertions(+), 400 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index aee0fffd7e..fd7eab0480 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -3,12 +3,10 @@ package org.opalj.br.fpcf.properties import org.opalj.fpcf.Entity import org.opalj.fpcf.FallbackReason -import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType @@ -56,13 +54,6 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta stringConstancyInformation: StringConstancyInformation ): StringConstancyProperty = new StringConstancyProperty(stringConstancyInformation) - /** - * Extracts a [[Result]] from the geiven `ppcr` and returns its property as an instance of this - * class. - */ - def extractFromPPCR(ppcr: ProperPropertyComputationResult): StringConstancyProperty = - ppcr.asInstanceOf[Result].finalEP.p.asInstanceOf[StringConstancyProperty] - /** * @return Returns the / a neutral [[StringConstancyProperty]] element, i.e., an element for * which [[StringConstancyProperty.isTheNeutralElement]] is `true`. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 6b62bfb73b..66752bd0f2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -7,12 +7,10 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property -import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation -import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.Method import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.DUVar @@ -140,18 +138,6 @@ case class InterproceduralComputationState(entity: P) { */ val isVFCFullyPrepared: mutable.Map[VirtualFunctionCall[V], Boolean] = mutable.Map() - /** - * Takes a definition site as well as a result and extends the [[fpe2sci]] map accordingly, - * however, only if `defSite` is not yet present. - */ - def appendResultToFpe2Sci( - defSite: Int, r: Result, reset: Boolean = false - ): Unit = appendToFpe2Sci( - defSite, - StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation, - reset - ) - /** * Takes a definition site as well as [[StringConstancyInformation]] and extends the [[fpe2sci]] * map accordingly, however, only if `defSite` is not yet present and `sci` not present within @@ -174,14 +160,6 @@ case class InterproceduralComputationState(entity: P) { def setInterimFpe2Sci(defSite: Int, sci: StringConstancyInformation): Unit = interimFpe2sci(defSite) = sci - /** - * Sets a result for the [[interimFpe2sci]] map. `r` is required to be a final result! - */ - def setInterimFpe2Sci(defSite: Int, r: Result): Unit = appendToFpe2Sci( - defSite, - StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation, - ) - /** * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index cfc4630b97..6ba197010e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -5,6 +5,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.InterimLUBP import org.opalj.fpcf.InterimResult @@ -213,7 +214,8 @@ class InterproceduralStringAnalysis( // Interpret a function / method parameter using the parameter information in state if (defSites.head < 0) { val r = state.iHandler.processDefSite(defSites.head, state.params.toList) - return Result(state.entity, StringConstancyProperty.extractFromPPCR(r)) + val sci = r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + return Result(state.entity, StringConstancyProperty(sci)) } val call = stmts(defSites.head).asAssignment.expr @@ -308,32 +310,32 @@ class InterproceduralStringAnalysis( } case StringConstancyProperty.key ⇒ eps match { - case FinalP(p: StringConstancyProperty) ⇒ - val resultEntity = eps.e.asInstanceOf[P] + case FinalEP(entity, p: StringConstancyProperty) ⇒ + val e = entity.asInstanceOf[P] // If necessary, update the parameter information with which the // surrounding function / method of the entity was called with - if (state.paramResultPositions.contains(resultEntity)) { - val pos = state.paramResultPositions(resultEntity) + if (state.paramResultPositions.contains(e)) { + val pos = state.paramResultPositions(e) state.params(pos._1)(pos._2) = p.stringConstancyInformation - state.paramResultPositions.remove(resultEntity) + state.paramResultPositions.remove(e) state.parameterDependeesCount -= 1 } // If necessary, update parameter information of function calls - if (state.entity2Function.contains(resultEntity)) { - state.var2IndexMapping(resultEntity._1).foreach(state.appendToFpe2Sci( + if (state.entity2Function.contains(e)) { + state.var2IndexMapping(e._1).foreach(state.appendToFpe2Sci( _, p.stringConstancyInformation )) // Update the state - state.entity2Function(resultEntity).foreach { f ⇒ - val pos = state.nonFinalFunctionArgsPos(f)(resultEntity) - val result = Result(resultEntity, p) - state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = result + state.entity2Function(e).foreach { f ⇒ + val pos = state.nonFinalFunctionArgsPos(f)(e) + val finalEp = FinalEP(e, p) + state.nonFinalFunctionArgs(f)(pos._1)(pos._2)(pos._3) = finalEp // Housekeeping - val index = state.entity2Function(resultEntity).indexOf(f) - state.entity2Function(resultEntity).remove(index) - if (state.entity2Function(resultEntity).isEmpty) { - state.entity2Function.remove(resultEntity) + val index = state.entity2Function(e).indexOf(f) + state.entity2Function(e).remove(index) + if (state.entity2Function(e).isEmpty) { + state.entity2Function.remove(e) } } // Continue only after all necessary function parameters are evaluated @@ -521,9 +523,12 @@ class InterproceduralStringAnalysis( p.elements.foreach { case FlatPathElement(index) ⇒ if (!state.fpe2sci.contains(index)) { - state.iHandler.processDefSite(index, state.params.toList) match { - case r: Result ⇒ state.appendResultToFpe2Sci(index, r, reset = true) - case _ ⇒ hasFinalResult = false + val eOptP = state.iHandler.processDefSite(index, state.params.toList) + if (eOptP.isFinal) { + val p = eOptP.asFinal.p.asInstanceOf[StringConstancyProperty] + state.appendToFpe2Sci(index, p.stringConstancyInformation, reset = true) + } else { + hasFinalResult = false } } case npe: NestedPathElement ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index b67c581b2e..de7933b826 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -161,8 +161,8 @@ class IntraproceduralStringAnalysis( val interHandler = IntraproceduralInterpretationHandler(tac) sci = StringConstancyInformation.reduceMultiple( uvar.definedBy.toArray.sorted.map { ds ⇒ - val r = interHandler.processDefSite(ds).asInstanceOf[Result] - r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + val r = interHandler.processDefSite(ds).asFinal + r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 1125a1de0d..56d0ba539b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -4,11 +4,10 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.cfg.CFG import org.opalj.br.Method @@ -146,35 +145,32 @@ abstract class AbstractStringInterpreter( case (nextParam, middleIndex) ⇒ nextParam.asVar.definedBy.toArray.sorted.zipWithIndex.map { case (ds, innerIndex) ⇒ - val r = iHandler.processDefSite(ds) - if (!r.isInstanceOf[Result]) { - val interim = r.asInstanceOf[InterimResult[StringConstancyProperty]] + val ep = iHandler.processDefSite(ds) + if (ep.isRefinable) { if (!functionArgsPos.contains(funCall)) { functionArgsPos(funCall) = mutable.Map() } - val e = interim.eps.e.asInstanceOf[P] + val e = ep.e.asInstanceOf[P] functionArgsPos(funCall)(e) = (outerIndex, middleIndex, innerIndex) if (!entity2function.contains(e)) { entity2function(e) = ListBuffer() } entity2function(e).append(funCall) } - r + ep }.to[ListBuffer] }.to[ListBuffer] }.to[ListBuffer] /** * This function checks whether the interpretation of parameters, as, e.g., produced by - * [[evaluateParameters()]], is final or not and returns all results not of type [[Result]] as a - * list. Hence, if this function returns an empty list, all parameters are fully evaluated. + * [[evaluateParameters()]], is final or not and returns all refineables as a list. Hence, if + * this function returns an empty list, all parameters are fully evaluated. */ protected def getNonFinalParameters( - evaluatedParameters: Seq[Seq[Seq[ProperPropertyComputationResult]]] - ): List[InterimResult[StringConstancyProperty]] = - evaluatedParameters.flatten.flatten.filter { !_.isInstanceOf[Result] }.map { - _.asInstanceOf[InterimResult[StringConstancyProperty]] - }.toList + evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, Property]]]] + ): List[EOptionP[Entity, Property]] = + evaluatedParameters.flatten.flatten.filter { _.isRefinable }.toList /** * convertEvaluatedParameters takes a list of evaluated / interpreted parameters as, e.g., @@ -183,12 +179,14 @@ abstract class AbstractStringInterpreter( * all results in the inner-most sequence are final! */ protected def convertEvaluatedParameters( - evaluatedParameters: Seq[Seq[Seq[ProperPropertyComputationResult]]] + evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, Property]]]] ): ListBuffer[ListBuffer[StringConstancyInformation]] = evaluatedParameters.map { paramList ⇒ paramList.map { param ⇒ - StringConstancyInformation.reduceMultiple(param.map { paramInterpr ⇒ - StringConstancyProperty.extractFromPPCR(paramInterpr).stringConstancyInformation - }) + StringConstancyInformation.reduceMultiple( + param.map { + _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + } + ) }.to[ListBuffer] }.to[ListBuffer] @@ -210,6 +208,6 @@ abstract class AbstractStringInterpreter( * the definition site, this function returns the interpreted instruction as entity. * Thus, the entity needs to be replaced by the calling client. */ - def interpret(instr: T, defSite: Int): ProperPropertyComputationResult + def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 14da3b0b8d..7605fb5bd3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -4,7 +4,9 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property import org.opalj.value.ValueInformation import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -60,7 +62,7 @@ abstract class InterpretationHandler(tac: TACode[TACMethodParameter, DUVar[Value def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): ProperPropertyComputationResult + ): EOptionP[Entity, Property] /** * [[InterpretationHandler]]s keeps an internal state for correct and faster processing. As diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala index cfa002583f..f6730147b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/BinaryExprInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -46,13 +47,13 @@ class BinaryExprInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.cTpe match { case ComputationalTypeInt ⇒ InterpretationHandler.getConstancyInfoForDynamicInt case ComputationalTypeFloat ⇒ InterpretationHandler.getConstancyInfoForDynamicFloat case _ ⇒ StringConstancyInformation.getNeutralElement } - Result(instr, StringConstancyProperty(sci)) + FinalEP(instr, StringConstancyProperty(sci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala index a2018931e1..67a9e395dc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/DoubleValueInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -36,8 +37,8 @@ class DoubleValueInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty(StringConstancyInformation( + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala index 2c64ec9196..225b1808ea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/FloatValueInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType @@ -36,8 +37,8 @@ class FloatValueInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty(StringConstancyInformation( + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala index 9cc6f7fdc5..66caf979d6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/IntegerValueInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -36,8 +37,8 @@ class IntegerValueInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty(StringConstancyInformation( + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value.toString diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala index 422af2854c..bdf088636e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/NewInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.New @@ -38,7 +39,7 @@ class NewInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.getNeutralElement) + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty.getNeutralElement) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala index 45ec16a1a5..bb01cfecbe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/common/StringConstInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -41,8 +42,8 @@ class StringConstInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty(StringConstancyInformation( + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty(StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.APPEND, instr.value diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index ddb714d843..5bf1ffcb79 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -3,8 +3,9 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -41,16 +42,16 @@ class ArrayPreparationInterpreter( /** * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string - * constancy information foreach definition site where it can compute a final result. All - * definition sites producing an intermediate result will have to be handled later on to + * constancy information for each definition site where it can compute a final result. All + * definition sites producing a refineable result will have to be handled later on to * not miss this information. * * @note For this implementation, `defSite` plays a role! * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { - val results = ListBuffer[ProperPropertyComputationResult]() + override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { + val results = ListBuffer[EOptionP[Entity, Property]]() val defSites = instr.arrayRef.asVar.definedBy.toArray val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( @@ -58,10 +59,12 @@ class ArrayPreparationInterpreter( ) allDefSites.map { ds ⇒ (ds, exprHandler.processDefSite(ds)) }.foreach { - case (ds, r: Result) ⇒ - state.appendResultToFpe2Sci(ds, r) - results.append(r) - case (_, ir: ProperPropertyComputationResult) ⇒ results.append(ir) + case (ds, ep) ⇒ + if (ep.isFinal) { + val p = ep.asFinal.p.asInstanceOf[StringConstancyProperty] + state.appendToFpe2Sci(ds, p.stringConstancyInformation) + } + results.append(ep) } // Add information of parameters @@ -69,24 +72,21 @@ class ArrayPreparationInterpreter( val paramPos = Math.abs(ds + 2) // lb is the fallback value val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) - val e: Integer = ds - state.appendResultToFpe2Sci(ds, Result(e, StringConstancyProperty(sci))) + state.appendToFpe2Sci(ds, sci) } // If there is at least one InterimResult, return one. Otherwise, return a final result // (to either indicate that further computation are necessary or a final result is already // present) - val interimResult = results.find(!_.isInstanceOf[Result]) - if (interimResult.isDefined) { - interimResult.get + val interims = results.find(!_.isFinal) + if (interims.isDefined) { + interims.get } else { - val scis = results.map(StringConstancyProperty.extractFromPPCR) - val resultSci = StringConstancyInformation.reduceMultiple( - scis.map(_.stringConstancyInformation) - ) - val result = Result(instr, StringConstancyProperty(resultSci)) - state.appendResultToFpe2Sci(defSite, result) - result + val resultSci = StringConstancyInformation.reduceMultiple(results.map { + _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) + state.appendToFpe2Sci(defSite, resultSci) + results.head } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index d1000f5bbc..834a2dc045 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -3,12 +3,13 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import scala.collection.mutable.ListBuffer +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -50,15 +51,15 @@ class InterproceduralFieldInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { val defSitEntity: Integer = defSite if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { // Unknown type => Cannot further approximate - return Result(instr, StringConstancyProperty.lb) + return FinalEP(instr, StringConstancyProperty.lb) } var hasInit = false - val results = ListBuffer[ProperPropertyComputationResult]() + val results = ListBuffer[EOptionP[Entity, Property]]() fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { case (m, pcs) ⇒ pcs.foreach { pc ⇒ if (m.name == "") { @@ -66,40 +67,26 @@ class InterproceduralFieldInterpreter( } val (tacEps, tac) = getTACAI(ps, m, state) val nextResult = if (tacEps.isRefinable) { - InterimResult( - instr, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + EPK(state.entity, StringConstancyProperty.key) } else { tac match { case Some(methodTac) ⇒ val stmt = methodTac.stmts(methodTac.pcToIndex(pc)) val entity = (stmt.asPutField.value.asVar, m) val eps = ps(entity, StringConstancyProperty.key) - eps match { - case FinalEP(e, p) ⇒ Result(e, p) - case _ ⇒ - state.dependees = eps :: state.dependees - // We need some mapping from an entity to an index in order for - // the processFinalP to find an entry. We cannot use the given - // def site as this would mark the def site as finalized even - // though it might not be. Thus, we use -1 as it is a safe dummy - // value - state.appendToVar2IndexMapping(entity._1, -1) - InterimResult( - entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + if (eps.isRefinable) { + state.dependees = eps :: state.dependees + // We need some mapping from an entity to an index in order for + // the processFinalP to find an entry. We cannot use the given + // def site as this would mark the def site as finalized even + // though it might not be. Thus, we use -1 as it is a safe dummy + // value + state.appendToVar2IndexMapping(entity._1, -1) } + eps case _ ⇒ // No TAC available - Result(defSitEntity, StringConstancyProperty.lb) + FinalEP(defSitEntity, StringConstancyProperty.lb) } } results.append(nextResult) @@ -116,27 +103,26 @@ class InterproceduralFieldInterpreter( state.appendToFpe2Sci( defSitEntity, StringConstancyProperty.lb.stringConstancyInformation ) - Result(defSitEntity, StringConstancyProperty(sci)) + FinalEP(defSitEntity, StringConstancyProperty(sci)) } else { // If all results are final, determine all possible values for the field. Otherwise, // return some intermediate result to indicate that the computation is not yet done - if (results.forall(_.isInstanceOf[Result])) { + if (results.forall(_.isFinal)) { // No init is present => append a `null` element to indicate that the field might be // null; this behavior could be refined by only setting the null element if no // statement is guaranteed to be executed prior to the field read if (!hasInit) { - results.append(Result( + results.append(FinalEP( instr, StringConstancyProperty(StringConstancyInformation.getNullElement) )) } - val resultScis = results.map { - StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation - } - val finalSci = StringConstancyInformation.reduceMultiple(resultScis) + val finalSci = StringConstancyInformation.reduceMultiple(results.map { + _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) state.appendToFpe2Sci(defSitEntity, finalSci) - Result(defSitEntity, StringConstancyProperty(finalSci)) + FinalEP(defSitEntity, StringConstancyProperty(finalSci)) } else { - results.find(!_.isInstanceOf[Result]).get + results.find(!_.isFinal).get } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala index 11104b9b45..dff867c7e4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetStatic @@ -34,8 +35,8 @@ class InterproceduralGetStaticInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - // TODO: How can they be better approximated? - Result(instr, StringConstancyProperty.lb) + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + // TODO: Approximate in a better way + FinalEP(instr, StringConstancyProperty.lb) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index d762e7b41c..30b9be453f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -1,8 +1,11 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.value.ValueInformation @@ -75,7 +78,7 @@ class InterproceduralInterpretationHandler( */ override def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt @@ -84,14 +87,14 @@ class InterproceduralInterpretationHandler( if (defSite < 0 && (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset)) { state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) - return Result(e, StringConstancyProperty.lb) + return FinalEP(e, StringConstancyProperty.lb) } else if (defSite < 0) { val sci = getParam(params, defSite) state.setInterimFpe2Sci(defSite, sci) - return Result(e, StringConstancyProperty(sci)) + return FinalEP(e, StringConstancyProperty(sci)) } else if (processedDefSites.contains(defSite)) { state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) - return Result(e, StringConstancyProperty.getNeutralElement) + return FinalEP(e, StringConstancyProperty.getNeutralElement) } // Note that def sites referring to constant expressions will be deleted further down processedDefSites(defSite) = Unit @@ -122,7 +125,7 @@ class InterproceduralInterpretationHandler( case nvmc: NonVirtualMethodCall[V] ⇒ processNonVirtualMethodCall(nvmc, defSite) case _ ⇒ state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) - Result(e, StringConstancyProperty.getNeutralElement) + FinalEP(e, StringConstancyProperty.getNeutralElement) } } @@ -132,8 +135,8 @@ class InterproceduralInterpretationHandler( */ private def processConstExpr( constExpr: SimpleValueConst, defSite: Int - ): ProperPropertyComputationResult = { - val ppcr = constExpr match { + ): EOptionP[Entity, StringConstancyProperty] = { + val finalEP = constExpr match { case ic: IntConst ⇒ new IntegerValueInterpreter(cfg, this).interpret(ic, defSite) case fc: FloatConst ⇒ new FloatValueInterpreter(cfg, this).interpret(fc, defSite) case dc: DoubleConst ⇒ new DoubleValueInterpreter(cfg, this).interpret(dc, defSite) @@ -141,11 +144,11 @@ class InterproceduralInterpretationHandler( sc.asInstanceOf[StringConst], defSite ) } - val result = ppcr.asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) + val sci = finalEP.asFinal.p.stringConstancyInformation + state.appendToFpe2Sci(defSite, sci) + state.setInterimFpe2Sci(defSite, sci) processedDefSites.remove(defSite) - result + finalEP } /** @@ -153,37 +156,41 @@ class InterproceduralInterpretationHandler( */ private def processArrayLoad( expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new ArrayPreparationInterpreter( cfg, this, state, params ).interpret(expr, defSite) - if (!r.isInstanceOf[Result]) { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + val sci = if (r.isFinal) { + r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + } else { processedDefSites.remove(defSite) + StringConstancyInformation.lb } + state.setInterimFpe2Sci(defSite, sci) r } /** * Helper / utility function for processing [[New]] expressions. */ - private def processNew(expr: New, defSite: Int): ProperPropertyComputationResult = { - val result = new NewInterpreter(cfg, this).interpret( + private def processNew(expr: New, defSite: Int): EOptionP[Entity, Property] = { + val finalEP = new NewInterpreter(cfg, this).interpret( expr, defSite - ).asInstanceOf[Result] - state.appendResultToFpe2Sci(defSite, result) - state.setInterimFpe2Sci(defSite, result) - result + ) + val sci = finalEP.asFinal.p.stringConstancyInformation + state.appendToFpe2Sci(defSite, sci) + state.setInterimFpe2Sci(defSite, sci) + finalEP } /** * Helper / utility function for processing [[GetStatic]]s. */ - private def processGetStatic(expr: GetStatic, defSite: Int): ProperPropertyComputationResult = { + private def processGetStatic(expr: GetStatic, defSite: Int): EOptionP[Entity, Property] = { val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( expr, defSite - ).asInstanceOf[Result] - state.setInterimFpe2Sci(defSite, result) + ) + doInterimResultHandling(result, defSite) result } @@ -194,7 +201,7 @@ class InterproceduralInterpretationHandler( expr: VirtualFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) @@ -202,7 +209,7 @@ class InterproceduralInterpretationHandler( // call was not fully prepared before (no final result available) or 2) the preparation is // now done (methodPrep2defSite makes sure we have the TAC ready for a method required by // this virtual function call). - val isFinalResult = r.isInstanceOf[Result] + val isFinalResult = r.isFinal if (!isFinalResult && !state.isVFCFullyPrepared.contains(expr)) { state.isVFCFullyPrepared(expr) = false } else if (state.isVFCFullyPrepared.contains(expr) && state.methodPrep2defSite.isEmpty) { @@ -233,7 +240,7 @@ class InterproceduralInterpretationHandler( */ private def processStaticFunctionCall( expr: StaticFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, params, declaredMethods, c ).interpret(expr, defSite) @@ -250,10 +257,11 @@ class InterproceduralInterpretationHandler( */ private def processBinaryExpr( expr: BinaryExpr[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) - state.setInterimFpe2Sci(defSite, result.asInstanceOf[Result]) - state.appendResultToFpe2Sci(defSite, result.asInstanceOf[Result]) + val sci = result.asFinal.p.stringConstancyInformation + state.setInterimFpe2Sci(defSite, sci) + state.appendToFpe2Sci(defSite, sci) result } @@ -262,11 +270,11 @@ class InterproceduralInterpretationHandler( */ private def processGetField( expr: GetField[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new InterproceduralFieldInterpreter( state, this, ps, fieldAccessInformation, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result]) { + if (r.isRefinable) { processedDefSites.remove(defSite) } doInterimResultHandling(r, defSite) @@ -278,11 +286,11 @@ class InterproceduralInterpretationHandler( */ private def processNonVirtualFunctionCall( expr: NonVirtualFunctionCall[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) - if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { + if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) } doInterimResultHandling(r, defSite) @@ -294,7 +302,7 @@ class InterproceduralInterpretationHandler( */ def processVirtualMethodCall( expr: VirtualMethodCall[V], defSite: Int, callees: Callees - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new InterproceduralVirtualMethodCallInterpreter( cfg, this, callees ).interpret(expr, defSite) @@ -307,14 +315,14 @@ class InterproceduralInterpretationHandler( */ private def processNonVirtualMethodCall( nvmc: NonVirtualMethodCall[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val r = new InterproceduralNonVirtualMethodCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(nvmc, defSite) r match { - case r: Result ⇒ - state.setInterimFpe2Sci(defSite, r) - state.appendResultToFpe2Sci(defSite, r) + case FinalEP(_, p: StringConstancyProperty) ⇒ + state.setInterimFpe2Sci(defSite, p.stringConstancyInformation) + state.appendToFpe2Sci(defSite, p.stringConstancyInformation) case _ ⇒ state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) processedDefSites.remove(defSite) @@ -328,14 +336,14 @@ class InterproceduralInterpretationHandler( * results. */ private def doInterimResultHandling( - result: ProperPropertyComputationResult, defSite: Int + result: EOptionP[Entity, Property], defSite: Int ): Unit = { - val isFinalResult = result.isInstanceOf[Result] - if (isFinalResult) { - state.setInterimFpe2Sci(defSite, result.asInstanceOf[Result]) + val sci = if (result.isFinal) { + result.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } else { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + StringConstancyInformation.lb } + state.setInterimFpe2Sci(defSite, sci) } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index faf10fcd1a..58548fe53e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,10 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods @@ -48,11 +50,11 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { val methods = getMethodsForPC(instr.pc, ps, state.callees, declaredMethods) if (methods._1.isEmpty) { // No methods available => Return lower bound - return Result(instr, StringConstancyProperty.lb) + return FinalEP(instr, StringConstancyProperty.lb) } val m = methods._1.head @@ -64,39 +66,24 @@ class InterproceduralNonVirtualFunctionCallInterpreter( if (returns.isEmpty) { // A function without returns, e.g., because it is guaranteed to throw an exception, // is approximated with the lower bound - Result(instr, StringConstancyProperty.lb) + FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret ⇒ val uvar = ret.asInstanceOf[ReturnValue[V]].expr.asVar val entity = (uvar, m) val eps = ps(entity, StringConstancyProperty.key) - eps match { - case FinalEP(e, p) ⇒ - Result(e, p) - case _ ⇒ - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(uvar, defSite) - InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + if (eps.isRefinable) { + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(uvar, defSite) } + eps } results.find(!_.isInstanceOf[Result]).getOrElse(results.head) } } else { state.appendToMethodPrep2defSite(m, defSite) - InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + EPK(state.entity, StringConstancyProperty.key) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index c4425db1ed..de9dde3d35 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -1,10 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -55,11 +57,11 @@ class InterproceduralNonVirtualMethodCallInterpreter( */ override def interpret( instr: NonVirtualMethodCall[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val e: Integer = defSite instr.name match { case "" ⇒ interpretInit(instr, e) - case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) + case _ ⇒ FinalEP(e, StringConstancyProperty.getNeutralElement) } } @@ -72,26 +74,32 @@ class InterproceduralNonVirtualMethodCallInterpreter( */ private def interpretInit( init: NonVirtualMethodCall[V], defSite: Integer - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { init.params.size match { - case 0 ⇒ Result(defSite, StringConstancyProperty.getNeutralElement) + case 0 ⇒ FinalEP(defSite, StringConstancyProperty.getNeutralElement) case _ ⇒ val results = init.params.head.asVar.definedBy.map { ds: Int ⇒ (ds, exprHandler.processDefSite(ds, List())) } - if (results.forall(_._2.isInstanceOf[Result])) { + if (results.forall(_._2.isFinal)) { // Final result is available - val scis = results.map(r ⇒ - StringConstancyProperty.extractFromPPCR(r._2).stringConstancyInformation) - val reduced = StringConstancyInformation.reduceMultiple(scis) - Result(defSite, StringConstancyProperty(reduced)) + val reduced = StringConstancyInformation.reduceMultiple(results.map { r ⇒ + val prop = r._2.asFinal.p.asInstanceOf[StringConstancyProperty] + prop.stringConstancyInformation + }) + FinalEP(defSite, StringConstancyProperty(reduced)) } else { // Some intermediate results => register necessary information from final // results and return an intermediate result - val returnIR = results.find(r ⇒ !r._2.isInstanceOf[Result]).get._2 + val returnIR = results.find(r ⇒ !r._2.isFinal).get._2 results.foreach { - case (ds, r: Result) ⇒ - state.appendResultToFpe2Sci(ds, r, reset = true) + case (ds, r) ⇒ + if (r.isFinal) { + val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] + state.appendToFpe2Sci( + ds, p.stringConstancyInformation, reset = true + ) + } case _ ⇒ } returnIR diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 6e5ff59986..2709d5f5d9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -3,12 +3,13 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import scala.util.Try +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.InterimResult import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -54,7 +55,7 @@ class InterproceduralStaticFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { if (instr.declaringClass.fqn == "java/lang/String" && instr.name == "valueOf") { processStringValueOf(instr) } else { @@ -67,23 +68,25 @@ class InterproceduralStaticFunctionCallInterpreter( * `call` element is actually such a call. * This function returns an intermediate results if one or more interpretations could not be * finished. Otherwise, if all definition sites could be fully processed, this function - * returns an instance of [[Result]] which corresponds to the result of the interpretation of + * returns an instance of Result which corresponds to the result of the interpretation of * the parameter passed to the call. */ private def processStringValueOf( call: StaticFunctionCall[V] - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds ⇒ exprHandler.processDefSite(ds, params) } - val interim = results.find(!_.isInstanceOf[Result]) + val interim = results.find(_.isRefinable) if (interim.isDefined) { interim.get } else { // For char values, we need to do a conversion (as the returned results are integers) - val scis = if (call.descriptor.parameterType(0).toJava == "char") { - results.map { r ⇒ - val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + val scis = results.map { r ⇒ + r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + } + val finalScis = if (call.descriptor.parameterType(0).toJava == "char") { + scis.map { sci ⇒ if (Try(sci.possibleStrings.toInt).isSuccess) { sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) } else { @@ -91,10 +94,10 @@ class InterproceduralStaticFunctionCallInterpreter( } } } else { - results.map(StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation) + scis } - val finalSci = StringConstancyInformation.reduceMultiple(scis) - Result(call, StringConstancyProperty(finalSci)) + val finalSci = StringConstancyInformation.reduceMultiple(finalScis) + FinalEP(call, StringConstancyProperty(finalSci)) } } @@ -103,7 +106,7 @@ class InterproceduralStaticFunctionCallInterpreter( */ private def processArbitraryCall( instr: StaticFunctionCall[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val methods, _ = getMethodsForPC( instr.pc, ps, state.callees, declaredMethods ) @@ -112,7 +115,7 @@ class InterproceduralStaticFunctionCallInterpreter( // getMethodsForPC and 2) interpreting the head is enough if (methods._1.isEmpty) { state.appendToFpe2Sci(defSite, StringConstancyProperty.lb.stringConstancyInformation) - return Result(instr, StringConstancyProperty.lb) + return FinalEP(instr, StringConstancyProperty.lb) } val m = methods._1.head @@ -166,40 +169,25 @@ class InterproceduralStaticFunctionCallInterpreter( if (returns.isEmpty) { // A function without returns, e.g., because it is guaranteed to throw an exception, // is approximated with the lower bound - Result(instr, StringConstancyProperty.lb) + FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret ⇒ val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, m) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) - eps match { - case FinalEP(e, p) ⇒ - Result(e, p) - case _ ⇒ - state.dependees = eps :: state.dependees - state.appendToVar2IndexMapping(entity._1, defSite) - InterimResult( - entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - List(), - c - ) + if (eps.isRefinable) { + state.dependees = eps :: state.dependees + state.appendToVar2IndexMapping(entity._1, defSite) } + eps } - results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + results.find(_.isRefinable).getOrElse(results.head) } } else { // No TAC => Register dependee and continue state.appendToMethodPrep2defSite(m, defSite) - InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + EPK(state.entity, StringConstancyProperty.key) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala index ba6f5eca3c..2300d8e297 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala @@ -1,8 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.Property import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -48,14 +50,14 @@ class InterproceduralVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { val sci = instr.name match { case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET ) case _ ⇒ StringConstancyInformation.getNeutralElement } - Result(instr, StringConstancyProperty(sci)) + FinalEP(instr, StringConstancyProperty(sci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index d98390e16c..b4584074d4 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -1,9 +1,12 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural -import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPK +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG @@ -24,6 +27,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.Interpretation import org.opalj.tac.ReturnValue import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.P /** * The `InterproceduralVirtualFunctionCallInterpreter` is responsible for processing @@ -75,7 +79,7 @@ class VirtualFunctionCallPreparationInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { val result = instr.name match { case "append" ⇒ interpretAppendCall(instr, defSite) case "toString" ⇒ interpretToStringCall(instr) @@ -86,13 +90,14 @@ class VirtualFunctionCallPreparationInterpreter( interpretArbitraryCall(instr, defSite) case _ ⇒ val e: Integer = defSite - Result(e, StringConstancyProperty.getNeutralElement) + FinalEP(e, StringConstancyProperty.getNeutralElement) } } - result match { - case r: Result ⇒ state.appendResultToFpe2Sci(defSite, r) - case _ ⇒ + if (result.isFinal) { + // If the result is final, it is guaranteed to be of type [P, StringConstancyProperty] + val prop = result.asFinal.p.asInstanceOf[StringConstancyProperty] + state.appendToFpe2Sci(defSite, prop.stringConstancyInformation) } result } @@ -103,13 +108,15 @@ class VirtualFunctionCallPreparationInterpreter( * analysis was triggered whose result is not yet ready. In this case, the result needs to be * finalized later on. */ - private def interpretArbitraryCall(instr: T, defSite: Int): ProperPropertyComputationResult = { + private def interpretArbitraryCall( + instr: T, defSite: Int + ): EOptionP[Entity, Property] = { val (methods, _) = getMethodsForPC( instr.pc, ps, state.callees, declaredMethods ) if (methods.isEmpty) { - return Result(instr, StringConstancyProperty.lb) + return FinalEP(instr, StringConstancyProperty.lb) } val directCallSites = state.callees.directCallSites()(ps, declaredMethods) @@ -154,39 +161,27 @@ class VirtualFunctionCallPreparationInterpreter( if (returns.isEmpty) { // It might be that a function has no return value, e. g., in case it is // guaranteed to throw an exception - Result(instr, StringConstancyProperty.lb) + FinalEP(instr, StringConstancyProperty.lb) } else { val results = returns.map { ret ⇒ val entity = (ret.asInstanceOf[ReturnValue[V]].expr.asVar, nextMethod) InterproceduralStringAnalysis.registerParams(entity, evaluatedParams) val eps = ps(entity, StringConstancyProperty.key) eps match { - case r: Result ⇒ - state.appendResultToFpe2Sci(defSite, r) + case r: FinalEP[P, StringConstancyProperty] ⇒ + state.appendToFpe2Sci(defSite, r.p.stringConstancyInformation) r case _ ⇒ state.dependees = eps :: state.dependees state.appendToVar2IndexMapping(entity._1, defSite) - InterimResult( - entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - List(), - c - ) + eps } } - results.find(!_.isInstanceOf[Result]).getOrElse(results.head) + results.find(_.isRefinable).getOrElse(results.head) } } else { state.appendToMethodPrep2defSite(nextMethod, defSite) - InterimResult( - state.entity, - StringConstancyProperty.lb, - StringConstancyProperty.ub, - state.dependees, - c - ) + EPK(state.entity, StringConstancyProperty.key) } } @@ -206,23 +201,24 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { val receiverResults = receiverValuesOfAppendCall(appendCall, state) val appendResult = valueOfAppendCall(appendCall, state) // If there is an intermediate result, return this one (then the final result cannot yet be // computed) - if (!receiverResults.head.isInstanceOf[Result]) { + if (receiverResults.head.isRefinable) { return receiverResults.head - } else if (!appendResult.isInstanceOf[Result]) { + } else if (appendResult.isRefinable) { return appendResult } - val receiverScis = receiverResults.map { - StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation + val receiverScis = receiverResults.map { r ⇒ + val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] + p.stringConstancyInformation } val appendSci = - StringConstancyProperty.extractFromPPCR(appendResult).stringConstancyInformation + appendResult.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation // The case can occur that receiver and append value are empty; although, it is // counter-intuitive, this case may occur if both, the receiver and the parameter, have been @@ -251,7 +247,7 @@ class VirtualFunctionCallPreparationInterpreter( } val e: Integer = defSite - Result(e, StringConstancyProperty(finalSci)) + FinalEP(e, StringConstancyProperty(finalSci)) } /** @@ -266,23 +262,24 @@ class VirtualFunctionCallPreparationInterpreter( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V], state: InterproceduralComputationState - ): List[ProperPropertyComputationResult] = { + ): List[EOptionP[Entity, Property]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted val allResults = defSites.map(ds ⇒ (ds, exprHandler.processDefSite(ds, params))) - val finalResults = allResults.filter(_._2.isInstanceOf[Result]) + val finalResults = allResults.filter(_._2.isFinal) val finalResultsWithoutNeutralElements = finalResults.filter { - case (_, Result(r)) ⇒ - val p = r.p.asInstanceOf[StringConstancyProperty] + case (_, FinalEP(_, p: StringConstancyProperty)) ⇒ !p.stringConstancyInformation.isTheNeutralElement case _ ⇒ false } - val intermediateResults = allResults.filter(!_._2.isInstanceOf[Result]) + val intermediateResults = allResults.filter(_._2.isRefinable) // Extend the state by the final results not being the neutral elements (they might need to // be finalized later) finalResultsWithoutNeutralElements.foreach { next ⇒ - state.appendResultToFpe2Sci(next._1, next._2.asInstanceOf[Result]) + val p = next._2.asFinal.p.asInstanceOf[StringConstancyProperty] + val sci = p.stringConstancyInformation + state.appendToFpe2Sci(next._1, sci) } if (intermediateResults.isEmpty) { @@ -298,19 +295,19 @@ class VirtualFunctionCallPreparationInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V], state: InterproceduralComputationState - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar val defSites = param.definedBy.toArray.sorted val values = defSites.map(exprHandler.processDefSite(_, params)) // Defer the computation if there is at least one intermediate result - if (!values.forall(_.isInstanceOf[Result])) { - return values.find(!_.isInstanceOf[Result]).get + if (values.exists(_.isRefinable)) { + return values.find(_.isRefinable).get } val sciValues = values.map { - StringConstancyProperty.extractFromPPCR(_).stringConstancyInformation + _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation } val defSitesValueSci = StringConstancyInformation.reduceMultiple(sciValues) // If defSiteHead points to a "New", value will be the empty list. In that case, process @@ -320,11 +317,12 @@ class VirtualFunctionCallPreparationInterpreter( val ds = cfg.code.instructions(defSites.head).asAssignment.targetVar.usedBy.toArray.min val r = exprHandler.processDefSite(ds, params) // Again, defer the computation if there is no final result (yet) - if (!r.isInstanceOf[Result]) { - newValueSci = defSitesValueSci + if (r.isRefinable) { + newValueSci = defSitesValueSci // TODO: Can be removed!?! return r } else { - newValueSci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] + newValueSci = p.stringConstancyInformation } } else { newValueSci = defSitesValueSci @@ -357,7 +355,7 @@ class VirtualFunctionCallPreparationInterpreter( val e: Integer = defSites.head state.appendToFpe2Sci(e, newValueSci, reset = true) - Result(e, StringConstancyProperty(finalSci)) + FinalEP(e, StringConstancyProperty(finalSci)) } /** @@ -367,7 +365,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretToStringCall( call: VirtualFunctionCall[V] - ): ProperPropertyComputationResult = + ): EOptionP[Entity, Property] = // TODO: Can it produce an intermediate result??? exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) @@ -378,7 +376,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretReplaceCall( instr: VirtualFunctionCall[V] - ): ProperPropertyComputationResult = - Result(instr, InterpretationHandler.getStringConstancyPropertyForReplace) + ): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, InterpretationHandler.getStringConstancyPropertyForReplace) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala index 9e8fd24f1f..0724fa5910 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralArrayInterpreter.scala @@ -3,8 +3,9 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedur import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -36,7 +37,7 @@ class IntraproceduralArrayInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val stmts = cfg.code.instructions val children = ListBuffer[StringConstancyInformation]() // Loop over all possible array values @@ -50,8 +51,7 @@ class IntraproceduralArrayInterpreter( } foreach { f: Int ⇒ val sortedDefs = stmts(f).asArrayStore.value.asVar.definedBy.toArray.sorted children.appendAll(sortedDefs.map { exprHandler.processDefSite(_) }.map { n ⇒ - val r = n.asInstanceOf[Result] - r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + n.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) } // Process ArrayLoads @@ -63,8 +63,7 @@ class IntraproceduralArrayInterpreter( } foreach { f: Int ⇒ val defs = stmts(f).asAssignment.expr.asArrayLoad.arrayRef.asVar.definedBy children.appendAll(defs.toArray.sorted.map(exprHandler.processDefSite(_)).map { n ⇒ - val r = n.asInstanceOf[Result] - r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + n.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) } } @@ -74,7 +73,7 @@ class IntraproceduralArrayInterpreter( children.append(StringConstancyProperty.lb.stringConstancyInformation) } - Result(instr, StringConstancyProperty( + FinalEP(instr, StringConstancyProperty( StringConstancyInformation.reduceMultiple( children.filter(!_.isTheNeutralElement) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala index 8f4428cae3..6556cbceb1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralFieldInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetField @@ -36,7 +37,7 @@ class IntraproceduralFieldInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lb) + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala index 360ba16cae..f79cee0099 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralGetStaticInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.GetStatic @@ -36,7 +37,7 @@ class IntraproceduralGetStaticInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lb) + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty.lb) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala index 21fa873404..d4b87324cb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralInterpretationHandler.scala @@ -1,8 +1,10 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.Property import org.opalj.value.ValueInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -55,19 +57,19 @@ class IntraproceduralInterpretationHandler( */ override def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, Property] = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt // Function parameters are not evaluated but regarded as unknown if (defSite < 0) { - return Result(e, StringConstancyProperty.lb) + return FinalEP(e, StringConstancyProperty.lb) } else if (processedDefSites.contains(defSite)) { - return Result(e, StringConstancyProperty.getNeutralElement) + return FinalEP(e, StringConstancyProperty.getNeutralElement) } processedDefSites(defSite) = Unit - val result: ProperPropertyComputationResult = stmts(defSite) match { + val result: EOptionP[Entity, Property] = stmts(defSite) match { case Assignment(_, _, expr: StringConst) ⇒ new StringConstInterpreter(cfg, this).interpret(expr, defSite) case Assignment(_, _, expr: IntConst) ⇒ @@ -106,10 +108,9 @@ class IntraproceduralInterpretationHandler( new IntraproceduralNonVirtualMethodCallInterpreter( cfg, this ).interpret(nvmc, defSite) - case _ ⇒ Result(e, StringConstancyProperty.getNeutralElement) + case _ ⇒ FinalEP(e, StringConstancyProperty.getNeutralElement) } - // Replace the entity of the result - Result(e, result.asInstanceOf[Result].finalEP.p) + result } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala index e305babdc4..13c2ef3f8c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualFunctionCallInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.NonVirtualFunctionCall @@ -33,7 +34,7 @@ class IntraproceduralNonVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lb) + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala index f541218e30..3868f54cf3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralNonVirtualMethodCallInterpreter.scala @@ -3,8 +3,9 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedur import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -49,12 +50,12 @@ class IntraproceduralNonVirtualMethodCallInterpreter( */ override def interpret( instr: NonVirtualMethodCall[V], defSite: Int - ): ProperPropertyComputationResult = { + ): EOptionP[Entity, StringConstancyProperty] = { val prop = instr.name match { case "" ⇒ interpretInit(instr) case _ ⇒ StringConstancyProperty.getNeutralElement } - Result(instr, prop) + FinalEP(instr, prop) } /** @@ -70,9 +71,9 @@ class IntraproceduralNonVirtualMethodCallInterpreter( case _ ⇒ val scis = ListBuffer[StringConstancyInformation]() init.params.head.asVar.definedBy.foreach { ds ⇒ - val r = exprHandler.processDefSite(ds).asInstanceOf[Result] + val r = exprHandler.processDefSite(ds).asFinal scis.append( - r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + r.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation ) } val reduced = StringConstancyInformation.reduceMultiple(scis) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala index 07e4569016..5e565d966a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralStaticFunctionCallInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.StaticFunctionCall @@ -35,7 +36,7 @@ class IntraproceduralStaticFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = - Result(instr, StringConstancyProperty.lb) + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = + FinalEP(instr, StringConstancyProperty.lb) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala index 68e36dd78b..0cc3b663aa 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualFunctionCallInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.ComputationalTypeFloat import org.opalj.br.ComputationalTypeInt @@ -64,7 +65,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val property = instr.name match { case "append" ⇒ interpretAppendCall(instr) case "toString" ⇒ interpretToStringCall(instr) @@ -83,7 +84,7 @@ class IntraproceduralVirtualFunctionCallInterpreter( } } - Result(instr, property) + FinalEP(instr, property) } /** @@ -133,8 +134,8 @@ class IntraproceduralVirtualFunctionCallInterpreter( // There might be several receivers, thus the map; from the processed sites, however, use // only the head as a single receiver interpretation will produce one element val scis = call.receiver.asVar.definedBy.toArray.sorted.map { ds ⇒ - val r = exprHandler.processDefSite(ds).asInstanceOf[Result] - r.finalEP.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + val r = exprHandler.processDefSite(ds) + r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }.filter { sci ⇒ !sci.isTheNeutralElement } val sci = if (scis.isEmpty) StringConstancyInformation.getNeutralElement else scis.head @@ -151,15 +152,15 @@ class IntraproceduralVirtualFunctionCallInterpreter( val param = call.params.head.asVar // .head because we want to evaluate only the first argument of append val defSiteHead = param.definedBy.head - var r = exprHandler.processDefSite(defSiteHead).asInstanceOf[Result] - var value = r.finalEP.p.asInstanceOf[StringConstancyProperty] + var r = exprHandler.processDefSite(defSiteHead) + var value = r.asFinal.p.asInstanceOf[StringConstancyProperty] // If defSiteHead points to a New, value will be the empty list. In that case, process // the first use site (which is the call) if (value.isTheNeutralElement) { r = exprHandler.processDefSite( cfg.code.instructions(defSiteHead).asAssignment.targetVar.usedBy.toArray.min - ).asInstanceOf[Result] - value = r.finalEP.p.asInstanceOf[StringConstancyProperty] + ).asFinal + value = r.asFinal.p.asInstanceOf[StringConstancyProperty] } val sci = value.stringConstancyInformation @@ -198,8 +199,8 @@ class IntraproceduralVirtualFunctionCallInterpreter( private def interpretToStringCall( call: VirtualFunctionCall[V] ): StringConstancyProperty = { - val r = exprHandler.processDefSite(call.receiver.asVar.definedBy.head).asInstanceOf[Result] - r.finalEP.p.asInstanceOf[StringConstancyProperty] + val finalEP = exprHandler.processDefSite(call.receiver.asVar.definedBy.head).asFinal + finalEP.p.asInstanceOf[StringConstancyProperty] } /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala index fc2ee042de..f35fbb332f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/intraprocedural/IntraproceduralVirtualMethodCallInterpreter.scala @@ -1,8 +1,9 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.intraprocedural -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.Result +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel @@ -48,14 +49,14 @@ class IntraproceduralVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): ProperPropertyComputationResult = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET ) case _ ⇒ StringConstancyInformation.getNeutralElement } - Result(instr, StringConstancyProperty(sci)) + FinalEP(instr, StringConstancyProperty(sci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index e30bcd7815..cc0eb70e49 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -4,7 +4,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.preprocessing import scala.collection.mutable.ListBuffer import scala.collection.mutable.Map -import org.opalj.fpcf.Result import org.opalj.br.fpcf.properties.properties.StringTree import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringTreeConcat @@ -40,8 +39,9 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { val sci = if (fpe2Sci.contains(fpe.element)) { StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) } else { - val r = interpretationHandler.processDefSite(fpe.element).asInstanceOf[Result] - val sci = StringConstancyProperty.extractFromPPCR(r).stringConstancyInformation + val r = interpretationHandler.processDefSite(fpe.element) + val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] + val sci = p.stringConstancyInformation fpe2Sci(fpe.element) = ListBuffer(sci) sci } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index 96633affe7..739490ca3b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -4,7 +4,9 @@ package org.opalj.tac.fpcf.analyses import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property import org.opalj.value.ValueInformation import org.opalj.br.Method import org.opalj.tac.DUVar @@ -35,7 +37,7 @@ package object string_analysis { * reason for the inner-most list is that a parameter might have different definition sites; to * capture all, the third (inner-most) list is necessary. */ - type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[ProperPropertyComputationResult]]] + type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[EOptionP[Entity, Property]]]] /** * This type serves as a lookup mechanism to find out which functions parameters map to which From c323747e68d1d50b70626956d0592e11120d7751 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 16:29:52 +0100 Subject: [PATCH 274/316] Implemented the hashCode and equals method (without it the Property Store might not be able to reach quiescence). --- .../opalj/br/fpcf/properties/StringConstancyProperty.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index fd7eab0480..4e45114f3b 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -34,6 +34,13 @@ class StringConstancyProperty( stringConstancyInformation.isTheNeutralElement } + override def hashCode(): Int = stringConstancyInformation.hashCode() + + override def equals(o: Any): Boolean = o match { + case scp: StringConstancyProperty ⇒ + stringConstancyInformation.equals(scp.stringConstancyInformation) + case _ ⇒ false + } } object StringConstancyProperty extends Property with StringConstancyPropertyMetaInformation { From 912613017a51bacb3273050d4c67160aaaedb550 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 19:07:27 +0100 Subject: [PATCH 275/316] Extended the handling of arrays in the way that an interpretation may not necessarily produce an EPK at all. --- .../ArrayPreparationInterpreter.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index 5bf1ffcb79..d06828fb71 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -5,6 +5,7 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.Property import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -82,9 +83,20 @@ class ArrayPreparationInterpreter( if (interims.isDefined) { interims.get } else { - val resultSci = StringConstancyInformation.reduceMultiple(results.map { + var resultSci = StringConstancyInformation.reduceMultiple(results.map { _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation }) + // It might be that there are no results; in such a case, set the string information to + // the lower bound and manually add an entry to the results list + if (resultSci.isTheNeutralElement) { + resultSci = StringConstancyInformation.lb + } + if (results.isEmpty) { + results.append(FinalEP( + (instr.arrayRef.asVar, state.entity._2), StringConstancyProperty(resultSci) + )) + } + state.appendToFpe2Sci(defSite, resultSci) results.head } From 085d70db814371a0467d7d6a38a1b88966fd24a2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 19:40:46 +0100 Subject: [PATCH 276/316] Made the path transforming procedure more robust. --- .../preprocessing/PathTransformer.scala | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index cc0eb70e49..8243c1818c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -40,10 +40,24 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { StringConstancyInformation.reduceMultiple(fpe2Sci(fpe.element)) } else { val r = interpretationHandler.processDefSite(fpe.element) - val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] - val sci = p.stringConstancyInformation - fpe2Sci(fpe.element) = ListBuffer(sci) - sci + val sciToAdd = if (r.isFinal) { + r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + } else { + // processDefSite is not guaranteed to return a StringConstancyProperty => + // fall back to lower bound is necessary + if (r.isEPK || r.isEPS) { + StringConstancyInformation.lb + } else { + r.asInterim.ub match { + case property: StringConstancyProperty ⇒ + property.stringConstancyInformation + case _ ⇒ + StringConstancyInformation.lb + } + } + } + fpe2Sci(fpe.element) = ListBuffer(sciToAdd) + sciToAdd } if (sci.isTheNeutralElement) { None From 9c89f965375af26c05d568b6dc29217688bdd1fa Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 20:10:55 +0100 Subject: [PATCH 277/316] A "Cond" or "Or" tree may not necessarily have append elements. --- .../string_definition/StringTree.scala | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala index b8e7bd422e..f3a34a8915 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala @@ -28,26 +28,31 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme val resetElement = reduced.find(_.constancyType == StringConstancyType.RESET) val replaceElement = reduced.find(_.constancyType == StringConstancyType.REPLACE) val appendElements = reduced.filter { _.constancyType == StringConstancyType.APPEND } - val reducedInfo = appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( - StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), - StringConstancyType.APPEND, - s"${o.possibleStrings}|${n.possibleStrings}" - )) + val appendSci = if (appendElements.nonEmpty) { + Some(appendElements.reduceLeft((o, n) ⇒ StringConstancyInformation( + StringConstancyLevel.determineMoreGeneral(o.constancyLevel, n.constancyLevel), + StringConstancyType.APPEND, + s"${o.possibleStrings}|${n.possibleStrings}" + ))) + } else { + None + } val scis = ListBuffer[StringConstancyInformation]() - // The only difference between a Cond and an Or is how the possible strings look like - var possibleStrings = s"${reducedInfo.possibleStrings}" - if (processOr) { - if (appendElements.tail.nonEmpty) { - possibleStrings = s"($possibleStrings)" + if (appendSci.isDefined) { + // The only difference between a Cond and an Or is how the possible strings look like + var possibleStrings = s"${appendSci.get.possibleStrings}" + if (processOr) { + if (appendElements.tail.nonEmpty) { + possibleStrings = s"($possibleStrings)" + } + } else { + possibleStrings = s"(${appendSci.get.possibleStrings})?" } - } else { - possibleStrings = s"(${reducedInfo.possibleStrings})?" + scis.append(StringConstancyInformation( + appendSci.get.constancyLevel, appendSci.get.constancyType, possibleStrings + )) } - - scis.append(StringConstancyInformation( - reducedInfo.constancyLevel, reducedInfo.constancyType, possibleStrings - )) if (resetElement.isDefined) { scis.append(resetElement.get) } From 613c49ec64276981fa66c07676b03b7f68b08672 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 20:53:35 +0100 Subject: [PATCH 278/316] Slightly improved the procedure to determine whether a conditional has an "else" branch. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 7184011a84..264ca3bf01 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -791,7 +791,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // For every successor (except the very last one), execute a DFS to check whether the // very last element is a successor. If so, this represents a path past the if (or // if-elseif). - var reachableCount = 0 + var reachableCount = successors.count(_ == lastEle) successors.foreach { next ⇒ val seenNodes = ListBuffer[CFGNode](cfg.bb(branchingSite), cfg.bb(next)) val toVisitStack = mutable.Stack[CFGNode](cfg.bb(next).successors.toArray: _*) From 235c6100348daf9259c5c8d6246cc2ae0d4dfe58 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 22:32:50 +0100 Subject: [PATCH 279/316] On every generation of an interim, compute a new upper bound. --- .../InterproceduralComputationState.scala | 9 +++- .../InterproceduralStringAnalysis.scala | 54 +++++++++++-------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 66752bd0f2..9b2d2cd340 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -34,10 +34,17 @@ case class InterproceduralComputationState(entity: P) { var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = _ /** - * The interpretation handler to use + * The interpretation handler to use for computing a final result (if possible). */ var iHandler: InterproceduralInterpretationHandler = _ + /** + * The interpretation handler to use for computing intermediate results. We need two handlers + * since they have an internal state, e.g., processed def sites, which should not interfere + * each other to produce correct results. + */ + var interimIHandler: InterproceduralInterpretationHandler = _ + /** * The computed lean path that corresponds to the given entity */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 6ba197010e..2b1f8933b2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -90,12 +90,34 @@ class InterproceduralStringAnalysis( */ private def getInterimResult( state: InterproceduralComputationState, - lb: StringConstancyProperty = StringConstancyProperty.lb, - ub: StringConstancyProperty = StringConstancyProperty.ub ): InterimResult[StringConstancyProperty] = InterimResult( - state.entity, lb, ub, state.dependees, continuation(state) + state.entity, + computeNewLowerBound(state), + computeNewUpperBound(state), + state.dependees, + continuation(state) ) + private def computeNewUpperBound( + state: InterproceduralComputationState + ): StringConstancyProperty = { + val fpe2SciMapping = state.interimFpe2sci.map { + case (key, value) ⇒ key → ListBuffer(value) + } + identity(fpe2SciMapping) + if (state.computedLeanPath != null) { + StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( + state.computedLeanPath, fpe2SciMapping + ).reduce(true)) + } else { + StringConstancyProperty.lb + } + } + + private def computeNewLowerBound( + state: InterproceduralComputationState + ): StringConstancyProperty = StringConstancyProperty.lb + def analyze(data: P): ProperPropertyComputationResult = { val state = InterproceduralComputationState(data) val dm = declaredMethods(data._2) @@ -142,6 +164,9 @@ class InterproceduralStringAnalysis( state.iHandler = InterproceduralInterpretationHandler( state.tac, ps, declaredMethods, fieldAccessInformation, state, continuation(state) ) + state.interimIHandler = InterproceduralInterpretationHandler( + state.tac, ps, declaredMethods, fieldAccessInformation, state.copy(), continuation(state) + ) } if (state.computedLeanPath == null) { @@ -359,29 +384,12 @@ class InterproceduralStringAnalysis( } else { determinePossibleStrings(state) } - case InterimLUBP(lb: StringConstancyProperty, ub: StringConstancyProperty) ⇒ + case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) ⇒ state.dependees = eps :: state.dependees - - // If a new upper bound value is present, recompute a new interim result - var recomputeInterim = false state.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i ⇒ - if (!state.interimFpe2sci.contains(i) || - ub.stringConstancyInformation != state.interimFpe2sci(i)) { state.setInterimFpe2Sci(i, ub.stringConstancyInformation) - recomputeInterim = true - } } - // Either set a new interim result or use the old one if nothing has changed - val ubForInterim = if (recomputeInterim) { - val fpe2SciMapping = state.interimFpe2sci.map { - case (key, value) ⇒ key → ListBuffer(value) - } - StringConstancyProperty(new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, fpe2SciMapping - ).reduce(true)) - } else ub - - getInterimResult(state, lb, ubForInterim) + getInterimResult(state) case _ ⇒ state.dependees = eps :: state.dependees getInterimResult(state) @@ -853,7 +861,7 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule * Executor for the lazy analysis. */ object LazyInterproceduralStringAnalysis - extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData From c3f1318ee6bce6bf62e93f0d192ea2188b0e7b6d Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 11 Mar 2019 22:54:36 +0100 Subject: [PATCH 280/316] Narrowed the return type of InterproceduralInterpretationHandler#processDefSite down to EOptionP[Entity, StringConstancyProperty]. --- .../AbstractStringInterpreter.scala | 4 +-- .../ArrayPreparationInterpreter.scala | 5 ++- .../InterproceduralFieldInterpreter.scala | 5 ++- ...InterproceduralInterpretationHandler.scala | 24 +++++++------ ...ralNonVirtualFunctionCallInterpreter.scala | 3 +- ...duralNonVirtualMethodCallInterpreter.scala | 5 ++- ...ceduralStaticFunctionCallInterpreter.scala | 7 ++-- ...oceduralVirtualMethodCallInterpreter.scala | 3 +- ...alFunctionCallPreparationInterpreter.scala | 35 ++++++++++--------- .../string_analysis/string_analysis.scala | 4 +-- 10 files changed, 47 insertions(+), 48 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 56d0ba539b..80e40da375 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -168,8 +168,8 @@ abstract class AbstractStringInterpreter( * this function returns an empty list, all parameters are fully evaluated. */ protected def getNonFinalParameters( - evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, Property]]]] - ): List[EOptionP[Entity, Property]] = + evaluatedParameters: Seq[Seq[Seq[EOptionP[Entity, StringConstancyProperty]]]] + ): List[EOptionP[Entity, StringConstancyProperty]] = evaluatedParameters.flatten.flatten.filter { _.isRefinable }.toList /** diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala index d06828fb71..6b989603cc 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala @@ -6,7 +6,6 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.Property import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -51,8 +50,8 @@ class ArrayPreparationInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { - val results = ListBuffer[EOptionP[Entity, Property]]() + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() val defSites = instr.arrayRef.asVar.definedBy.toArray val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 834a2dc045..22753bd726 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -8,7 +8,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -51,7 +50,7 @@ class InterproceduralFieldInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val defSitEntity: Integer = defSite if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { // Unknown type => Cannot further approximate @@ -59,7 +58,7 @@ class InterproceduralFieldInterpreter( } var hasInit = false - val results = ListBuffer[EOptionP[Entity, Property]]() + val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { case (m, pcs) ⇒ pcs.foreach { pc ⇒ if (m.name == "") { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 30b9be453f..37d8bf88df 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -78,7 +78,7 @@ class InterproceduralInterpretationHandler( */ override def processDefSite( defSite: Int, params: List[Seq[StringConstancyInformation]] = List() - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { // Without doing the following conversion, the following compile error will occur: "the // result type of an implicit conversion must be more specific than org.opalj.fpcf.Entity" val e: Integer = defSite.toInt @@ -156,7 +156,7 @@ class InterproceduralInterpretationHandler( */ private def processArrayLoad( expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new ArrayPreparationInterpreter( cfg, this, state, params ).interpret(expr, defSite) @@ -173,7 +173,7 @@ class InterproceduralInterpretationHandler( /** * Helper / utility function for processing [[New]] expressions. */ - private def processNew(expr: New, defSite: Int): EOptionP[Entity, Property] = { + private def processNew(expr: New, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val finalEP = new NewInterpreter(cfg, this).interpret( expr, defSite ) @@ -186,7 +186,9 @@ class InterproceduralInterpretationHandler( /** * Helper / utility function for processing [[GetStatic]]s. */ - private def processGetStatic(expr: GetStatic, defSite: Int): EOptionP[Entity, Property] = { + private def processGetStatic( + expr: GetStatic, defSite: Int + ): EOptionP[Entity, StringConstancyProperty] = { val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( expr, defSite ) @@ -201,7 +203,7 @@ class InterproceduralInterpretationHandler( expr: VirtualFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new VirtualFunctionCallPreparationInterpreter( cfg, this, ps, state, declaredMethods, params, c ).interpret(expr, defSite) @@ -240,7 +242,7 @@ class InterproceduralInterpretationHandler( */ private def processStaticFunctionCall( expr: StaticFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralStaticFunctionCallInterpreter( cfg, this, ps, state, params, declaredMethods, c ).interpret(expr, defSite) @@ -257,7 +259,7 @@ class InterproceduralInterpretationHandler( */ private def processBinaryExpr( expr: BinaryExpr[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) val sci = result.asFinal.p.stringConstancyInformation state.setInterimFpe2Sci(defSite, sci) @@ -270,7 +272,7 @@ class InterproceduralInterpretationHandler( */ private def processGetField( expr: GetField[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( state, this, ps, fieldAccessInformation, c ).interpret(expr, defSite) @@ -286,7 +288,7 @@ class InterproceduralInterpretationHandler( */ private def processNonVirtualFunctionCall( expr: NonVirtualFunctionCall[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(expr, defSite) @@ -302,7 +304,7 @@ class InterproceduralInterpretationHandler( */ def processVirtualMethodCall( expr: VirtualMethodCall[V], defSite: Int, callees: Callees - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralVirtualMethodCallInterpreter( cfg, this, callees ).interpret(expr, defSite) @@ -315,7 +317,7 @@ class InterproceduralInterpretationHandler( */ private def processNonVirtualMethodCall( nvmc: NonVirtualMethodCall[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualMethodCallInterpreter( cfg, this, ps, state, declaredMethods, c ).interpret(nvmc, defSite) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index 58548fe53e..cca0875204 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -6,7 +6,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods @@ -50,7 +49,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val methods = getMethodsForPC(instr.pc, ps, state.callees, declaredMethods) if (methods._1.isEmpty) { // No methods available => Return lower bound diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index de9dde3d35..bd0223bcfe 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -5,7 +5,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG @@ -57,7 +56,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( */ override def interpret( instr: NonVirtualMethodCall[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val e: Integer = defSite instr.name match { case "" ⇒ interpretInit(instr, e) @@ -74,7 +73,7 @@ class InterproceduralNonVirtualMethodCallInterpreter( */ private def interpretInit( init: NonVirtualMethodCall[V], defSite: Integer - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { init.params.size match { case 0 ⇒ FinalEP(defSite, StringConstancyProperty.getNeutralElement) case _ ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 2709d5f5d9..a7b6c3e0ea 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -8,7 +8,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG @@ -55,7 +54,7 @@ class InterproceduralStaticFunctionCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { if (instr.declaringClass.fqn == "java/lang/String" && instr.name == "valueOf") { processStringValueOf(instr) } else { @@ -73,7 +72,7 @@ class InterproceduralStaticFunctionCallInterpreter( */ private def processStringValueOf( call: StaticFunctionCall[V] - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val results = call.params.head.asVar.definedBy.toArray.sorted.map { ds ⇒ exprHandler.processDefSite(ds, params) } @@ -106,7 +105,7 @@ class InterproceduralStaticFunctionCallInterpreter( */ private def processArbitraryCall( instr: StaticFunctionCall[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val methods, _ = getMethodsForPC( instr.pc, ps, state.callees, declaredMethods ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala index 2300d8e297..8c7c5b4a2b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala @@ -4,7 +4,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.Property import org.opalj.br.cfg.CFG import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation @@ -50,7 +49,7 @@ class InterproceduralVirtualMethodCallInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val sci = instr.name match { case "setLength" ⇒ StringConstancyInformation( StringConstancyLevel.CONSTANT, StringConstancyType.RESET diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index b4584074d4..efe1966745 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -6,7 +6,6 @@ import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP import org.opalj.fpcf.ProperOnUpdateContinuation -import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG @@ -79,7 +78,7 @@ class VirtualFunctionCallPreparationInterpreter( * * @see [[AbstractStringInterpreter.interpret]] */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, Property] = { + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val result = instr.name match { case "append" ⇒ interpretAppendCall(instr, defSite) case "toString" ⇒ interpretToStringCall(instr) @@ -110,7 +109,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretArbitraryCall( instr: T, defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val (methods, _) = getMethodsForPC( instr.pc, ps, state.callees, declaredMethods ) @@ -201,7 +200,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretAppendCall( appendCall: VirtualFunctionCall[V], defSite: Int - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { val receiverResults = receiverValuesOfAppendCall(appendCall, state) val appendResult = valueOfAppendCall(appendCall, state) @@ -262,7 +261,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def receiverValuesOfAppendCall( call: VirtualFunctionCall[V], state: InterproceduralComputationState - ): List[EOptionP[Entity, Property]] = { + ): List[EOptionP[Entity, StringConstancyProperty]] = { val defSites = call.receiver.asVar.definedBy.toArray.sorted val allResults = defSites.map(ds ⇒ (ds, exprHandler.processDefSite(ds, params))) @@ -295,7 +294,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def valueOfAppendCall( call: VirtualFunctionCall[V], state: InterproceduralComputationState - ): EOptionP[Entity, Property] = { + ): EOptionP[Entity, StringConstancyProperty] = { // .head because we want to evaluate only the first argument of append val param = call.params.head.asVar val defSites = param.definedBy.toArray.sorted @@ -311,18 +310,22 @@ class VirtualFunctionCallPreparationInterpreter( } val defSitesValueSci = StringConstancyInformation.reduceMultiple(sciValues) // If defSiteHead points to a "New", value will be the empty list. In that case, process - // the first use site (which is the call) + // the first use site var newValueSci = StringConstancyInformation.getNeutralElement if (defSitesValueSci.isTheNeutralElement) { - val ds = cfg.code.instructions(defSites.head).asAssignment.targetVar.usedBy.toArray.min - val r = exprHandler.processDefSite(ds, params) - // Again, defer the computation if there is no final result (yet) - if (r.isRefinable) { - newValueSci = defSitesValueSci // TODO: Can be removed!?! - return r + val headSite = defSites.head + if (headSite < 0) { + newValueSci = StringConstancyInformation.lb } else { - val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] - newValueSci = p.stringConstancyInformation + val ds = cfg.code.instructions(headSite).asAssignment.targetVar.usedBy.toArray.min + val r = exprHandler.processDefSite(ds, params) + // Again, defer the computation if there is no final result (yet) + if (r.isRefinable) { + return r + } else { + val p = r.asFinal.p.asInstanceOf[StringConstancyProperty] + newValueSci = p.stringConstancyInformation + } } } else { newValueSci = defSitesValueSci @@ -365,7 +368,7 @@ class VirtualFunctionCallPreparationInterpreter( */ private def interpretToStringCall( call: VirtualFunctionCall[V] - ): EOptionP[Entity, Property] = + ): EOptionP[Entity, StringConstancyProperty] = // TODO: Can it produce an intermediate result??? exprHandler.processDefSite(call.receiver.asVar.definedBy.head, params) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala index 739490ca3b..bd7c787454 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/string_analysis.scala @@ -6,9 +6,9 @@ import scala.collection.mutable.ListBuffer import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.Property import org.opalj.value.ValueInformation import org.opalj.br.Method +import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.tac.DUVar import org.opalj.tac.FunctionCall @@ -37,7 +37,7 @@ package object string_analysis { * reason for the inner-most list is that a parameter might have different definition sites; to * capture all, the third (inner-most) list is necessary. */ - type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[EOptionP[Entity, Property]]]] + type NonFinalFunctionArgs = ListBuffer[ListBuffer[ListBuffer[EOptionP[Entity, StringConstancyProperty]]]] /** * This type serves as a lookup mechanism to find out which functions parameters map to which From 33a9c3cbe494fa802cbea8b8e6c7e1fa0d268fc2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 06:54:36 +0100 Subject: [PATCH 281/316] Removed the "continuation" parameter from the InterproceduralInterpretationHandler as it is no longer necessary / used. --- .../InterproceduralStringAnalysis.scala | 4 ++-- .../InterproceduralFieldInterpreter.scala | 2 -- .../InterproceduralInterpretationHandler.scala | 15 ++++++--------- ...ceduralNonVirtualFunctionCallInterpreter.scala | 2 -- ...roceduralNonVirtualMethodCallInterpreter.scala | 4 ---- ...rproceduralStaticFunctionCallInterpreter.scala | 2 -- ...irtualFunctionCallPreparationInterpreter.scala | 2 -- 7 files changed, 8 insertions(+), 23 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 2b1f8933b2..11c728c2c2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -162,10 +162,10 @@ class InterproceduralStringAnalysis( if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, state, continuation(state) + state.tac, ps, declaredMethods, fieldAccessInformation, state ) state.interimIHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, state.copy(), continuation(state) + state.tac, ps, declaredMethods, fieldAccessInformation, state.copy() ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 22753bd726..16fbfbef1d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -7,7 +7,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty @@ -34,7 +33,6 @@ class InterproceduralFieldInterpreter( exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, fieldAccessInformation: FieldAccessInformation, - c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { override type T = GetField[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 37d8bf88df..9c79cff109 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -4,7 +4,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result @@ -67,7 +66,6 @@ class InterproceduralInterpretationHandler( declaredMethods: DeclaredMethods, fieldAccessInformation: FieldAccessInformation, state: InterproceduralComputationState, - c: ProperOnUpdateContinuation ) extends InterpretationHandler(tac) { /** @@ -205,7 +203,7 @@ class InterproceduralInterpretationHandler( params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new VirtualFunctionCallPreparationInterpreter( - cfg, this, ps, state, declaredMethods, params, c + cfg, this, ps, state, declaredMethods, params ).interpret(expr, defSite) // Set whether the virtual function call is fully prepared. This is the case if 1) the // call was not fully prepared before (no final result available) or 2) the preparation is @@ -244,7 +242,7 @@ class InterproceduralInterpretationHandler( expr: StaticFunctionCall[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralStaticFunctionCallInterpreter( - cfg, this, ps, state, params, declaredMethods, c + cfg, this, ps, state, params, declaredMethods ).interpret(expr, defSite) if (!r.isInstanceOf[Result] || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -274,7 +272,7 @@ class InterproceduralInterpretationHandler( expr: GetField[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( - state, this, ps, fieldAccessInformation, c + state, this, ps, fieldAccessInformation ).interpret(expr, defSite) if (r.isRefinable) { processedDefSites.remove(defSite) @@ -290,7 +288,7 @@ class InterproceduralInterpretationHandler( expr: NonVirtualFunctionCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualFunctionCallInterpreter( - cfg, this, ps, state, declaredMethods, c + cfg, this, ps, state, declaredMethods ).interpret(expr, defSite) if (r.isRefinable || state.nonFinalFunctionArgs.contains(expr)) { processedDefSites.remove(defSite) @@ -319,7 +317,7 @@ class InterproceduralInterpretationHandler( nvmc: NonVirtualMethodCall[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralNonVirtualMethodCallInterpreter( - cfg, this, ps, state, declaredMethods, c + cfg, this, ps, state, declaredMethods ).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) ⇒ @@ -406,9 +404,8 @@ object InterproceduralInterpretationHandler { declaredMethods: DeclaredMethods, fieldAccessInformation: FieldAccessInformation, state: InterproceduralComputationState, - c: ProperOnUpdateContinuation ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( - tac, ps, declaredMethods, fieldAccessInformation, state, c + tac, ps, declaredMethods, fieldAccessInformation, state ) } \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index cca0875204..a124f5cfcf 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -5,7 +5,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.analyses.DeclaredMethods @@ -33,7 +32,6 @@ class InterproceduralNonVirtualFunctionCallInterpreter( ps: PropertyStore, state: InterproceduralComputationState, declaredMethods: DeclaredMethods, - c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index bd0223bcfe..e01379a77f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -4,7 +4,6 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG @@ -28,13 +27,10 @@ import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationSta */ class InterproceduralNonVirtualMethodCallInterpreter( cfg: CFG[Stmt[V], TACStmts[V]], - // TODO: Do not let an instance of InterproceduralInterpretationHandler handler pass here - // but let it be instantiated in this class exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, declaredMethods: DeclaredMethods, - c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index a7b6c3e0ea..4c87ccafd2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -7,7 +7,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.PropertyStore import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.cfg.CFG @@ -39,7 +38,6 @@ class InterproceduralStaticFunctionCallInterpreter( state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]], declaredMethods: DeclaredMethods, - c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index efe1966745..b289becd91 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -5,7 +5,6 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.ProperOnUpdateContinuation import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Result import org.opalj.br.cfg.CFG @@ -44,7 +43,6 @@ class VirtualFunctionCallPreparationInterpreter( state: InterproceduralComputationState, declaredMethods: DeclaredMethods, params: List[Seq[StringConstancyInformation]], - c: ProperOnUpdateContinuation ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] From 97ea1279a8ae096088ca187a0352055bc2489bc2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 07:57:17 +0100 Subject: [PATCH 282/316] Had to refine the state of the interim state ("copy" does not copy all necesssary information). --- .../InterproceduralStringAnalysis.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 11c728c2c2..9f36599e5d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -160,19 +160,25 @@ class InterproceduralStringAnalysis( return getInterimResult(state) } + if (state.computedLeanPath == null) { + state.computedLeanPath = computeLeanPath(uvar, state.tac) + } + if (state.iHandler == null) { state.iHandler = InterproceduralInterpretationHandler( state.tac, ps, declaredMethods, fieldAccessInformation, state ) + val interimState = state.copy() + interimState.tac = state.tac + interimState.computedLeanPath = state.computedLeanPath + interimState.callees = state.callees + interimState.callers = state.callers + interimState.params = state.params state.interimIHandler = InterproceduralInterpretationHandler( - state.tac, ps, declaredMethods, fieldAccessInformation, state.copy() + state.tac, ps, declaredMethods, fieldAccessInformation, interimState ) } - if (state.computedLeanPath == null) { - state.computedLeanPath = computeLeanPath(uvar, state.tac) - } - var requiresCallersInfo = false if (state.params.isEmpty) { state.params = InterproceduralStringAnalysis.getParams(state.entity) From 869fe8da68d9f029bb54ae72aaa510e384c9bba4 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 09:13:40 +0100 Subject: [PATCH 283/316] Refined the handling of try-catch-finally. --- .../preprocessing/AbstractPathFinder.scala | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 264ca3bf01..183ded1c87 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -45,7 +45,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''e1''. */ protected case class HierarchicalCSOrder( - hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] ) /** @@ -328,8 +328,13 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // Find out, how many elements the finally block has and adjust the try // block accordingly val startFinally = cnSameStartPC.map(_.handlerPC).max - val endFinally = - cfg.code.instructions(startFinally - 1).asGoto.targetStmt + val endFinally = cfg.code.instructions(startFinally - 1) match { + // If the finally does not terminate a method, it has a goto to jump + // after the finally block; if not, the end of the finally is marked + // by the end of the method + case Goto(_, target) ⇒ target + case _ ⇒ cfg.code.instructions.length - 1 + } val numElementsFinally = endFinally - startFinally - 1 val endOfFinally = cnSameStartPC.map(_.handlerPC).max tryInfo(cn.startPC) = endOfFinally - 1 - numElementsFinally @@ -572,7 +577,13 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { if (hasFinallyBlock) { // Find out, how many elements the finally block has val startFinally = catchBlockStartPCs.max - val endFinally = cfg.code.instructions(startFinally - 1).asGoto.targetStmt + val endFinally = cfg.code.instructions(startFinally - 1) match { + // If the finally does not terminate a method, it has a goto to jump + // after the finally block; if not, the end of the finally is marked + // by the end of the method + case Goto(_, target) ⇒ target + case _ ⇒ cfg.code.instructions.length - 1 + } // -1 for unified processing further down below (because in // catchBlockStartPCs.foreach, 1 is subtracted) numElementsFinally = endFinally - startFinally - 1 @@ -725,8 +736,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -830,7 +841,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_)= Unit) + alreadySeen.foreach(seenNodes(_) = Unit) seenNodes(from) = Unit while (stack.nonEmpty) { From ad1b5c0e3f06fd480307667abb1f74536de0a827 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 09:22:22 +0100 Subject: [PATCH 284/316] Refined the procedure to find the definition sites of a StringBuilder. --- .../interpretation/InterpretationHandler.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 7605fb5bd3..856ec4be82 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -165,7 +165,14 @@ object InterpretationHandler { case _: New ⇒ defSites.append(next) case vfc: VirtualFunctionCall[V] ⇒ - stack.pushAll(vfc.receiver.asVar.definedBy.filter(_ >= 0).toArray) + val recDefSites = vfc.receiver.asVar.definedBy.filter(_ >= 0).toArray + // recDefSites.isEmpty => Definition site is a parameter => Use the + // current function call as a def site + if (recDefSites.nonEmpty) { + stack.pushAll(recDefSites) + } else { + defSites.append(next) + } case _: GetField[V] ⇒ defSites.append(next) case _ ⇒ // E.g., NullExpr @@ -194,6 +201,11 @@ object InterpretationHandler { case _ ⇒ List(ds) }) } + // If no init sites could be determined, use the definition sites of the UVar + if (defSites.isEmpty) { + defSites.appendAll(duvar.definedBy.toArray) + } + defSites.distinct.sorted.toList } From 3d04763a73fefac25a2cbc4a5c5f61faf46bc246 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 11:52:36 +0100 Subject: [PATCH 285/316] Avoid an endless loop by tracking seen elements. --- .../interpretation/InterpretationHandler.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index 856ec4be82..f040715dd2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -157,6 +157,7 @@ object InterpretationHandler { val defSites = ListBuffer[Int]() val stack = mutable.Stack[Int](toString.receiver.asVar.definedBy.filter(_ >= 0).toArray: _*) + val seenElements: mutable.Map[Int, Unit] = mutable.Map() while (stack.nonEmpty) { val next = stack.pop() stmts(next) match { @@ -169,7 +170,7 @@ object InterpretationHandler { // recDefSites.isEmpty => Definition site is a parameter => Use the // current function call as a def site if (recDefSites.nonEmpty) { - stack.pushAll(recDefSites) + stack.pushAll(recDefSites.filter(!seenElements.contains(_))) } else { defSites.append(next) } @@ -179,6 +180,7 @@ object InterpretationHandler { } case _ ⇒ } + seenElements(next) = Unit } defSites.sorted.toList From 7d1ae00c071a9c60c7fb3058d5a0af7f5a9e8141 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 11:53:10 +0100 Subject: [PATCH 286/316] Avoid endless recursion by checking to not start the procedure with itself. --- .../interpretation/InterpretationHandler.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala index f040715dd2..bad135fd9b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/InterpretationHandler.scala @@ -231,7 +231,11 @@ object InterpretationHandler { case Assignment(_, _, expr: New) ⇒ news.append(expr) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ - news.appendAll(findNewOfVar(expr.receiver.asVar, stmts)) + val exprReceiverVar = expr.receiver.asVar + // The "if" is to avoid endless recursion + if (duvar.definedBy != exprReceiverVar.definedBy) { + news.appendAll(findNewOfVar(exprReceiverVar, stmts)) + } case _ ⇒ } } From e0a35e154105ed3b5e15806defa79c1afc2f82c2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 16:26:11 +0100 Subject: [PATCH 287/316] Refined the handling of callers by introducing a threshold. --- .../InterproceduralStringAnalysis.scala | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 9f36599e5d..16f3dba0a6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -81,6 +81,13 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { + /** + * To analyze an expression within a method ''m'', callers information might be necessary, e.g., + * to know with which arguments ''m'' is called. [[callersThreshold]] determines the threshold + * up to which number of callers parameter information are gathered. For "number of callers + * greater than [[callersThreshold]]", parameters are approximated with the lower bound. + */ + private val callersThreshold = 10 private val declaredMethods = project.get(DeclaredMethodsKey) private final val fieldAccessInformation = project.get(FieldAccessInformationKey) @@ -467,12 +474,24 @@ class InterproceduralStringAnalysis( * This method takes a computation state, `state` as well as a TAC provider, `tacProvider`, and * determines the interpretations of all parameters of the method under analysis. These * interpretations are registered using [[InterproceduralStringAnalysis.registerParams]]. + * The return value of this function indicates whether a the parameter evaluation is done + * (`true`) or not yet (`false`). */ private def registerParams( state: InterproceduralComputationState ): Boolean = { + val callers = state.callers.callers(declaredMethods).toSeq + if (callers.length > callersThreshold) { + state.params.append( + state.entity._2.parameterTypes.map{ + _: FieldType ⇒ StringConstancyInformation.lb + }.to[ListBuffer] + ) + return false + } + var hasIntermediateResult = false - state.callers.callers(declaredMethods).toSeq.zipWithIndex.foreach { + callers.zipWithIndex.foreach { case ((m, pc), methodIndex) ⇒ val tac = propertyStore(m.definedMethod, TACAI.key).ub.tac.get val params = tac.stmts(tac.pcToIndex(pc)) match { From 86242274e5016f35385c1aed620fd6cb536a8032 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 16:26:43 +0100 Subject: [PATCH 288/316] Improved the handling of try-catch blocks. --- .../preprocessing/AbstractPathFinder.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 183ded1c87..1fe62a3768 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -45,7 +45,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { * ''e1''. */ protected case class HierarchicalCSOrder( - hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] + hierarchy: List[(Option[CSInfo], List[HierarchicalCSOrder])] ) /** @@ -339,9 +339,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { val endOfFinally = cnSameStartPC.map(_.handlerPC).max tryInfo(cn.startPC) = endOfFinally - 1 - numElementsFinally } else { - tryInfo(cn.startPC) = cfg.bb(cnSameStartPC.head.endPC).successors.map { + val blockIndex = if (cnSameStartPC.head.endPC < 0) + cfg.code.instructions.length - 1 else cnSameStartPC.head.endPC + tryInfo(cn.startPC) = cfg.bb(blockIndex).successors.map { case bb: BasicBlock ⇒ bb.startPC - case _ ⇒ -1 + case _ ⇒ blockIndex }.max - 1 } } @@ -553,7 +555,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { case cn: CatchNode ⇒ // Add once for the try block if (startEndPairs.isEmpty) { - startEndPairs.append((cn.startPC, cn.endPC)) + val endPC = if (cn.endPC >= 0) cn.endPC else cn.handlerPC + startEndPairs.append((cn.startPC, endPC)) } if (cn.catchType.isDefined && cn.catchType.get.fqn == "java/lang/Throwable") { throwableElement = Some(cn) @@ -736,8 +739,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { bb.successors.filter( _.isInstanceOf[BasicBlock] ).foldLeft(false)((prev: Boolean, next: CFGNode) ⇒ { - prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) - }) + prev || (next.predecessors.count(_.isInstanceOf[BasicBlock]) >= n) + }) /** * This function checks if a branching corresponds to an if (or if-elseif) structure that has no @@ -841,7 +844,7 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { ): Boolean = { val stack = mutable.Stack(from) val seenNodes = mutable.Map[Int, Unit]() - alreadySeen.foreach(seenNodes(_) = Unit) + alreadySeen.foreach(seenNodes(_)= Unit) seenNodes(from) = Unit while (stack.nonEmpty) { From 871eb4a5ac93a65496f64534d986a7a4de7097b7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 18:57:33 +0100 Subject: [PATCH 289/316] Removed an unnecessary line. --- .../analyses/string_analysis/InterproceduralStringAnalysis.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 16f3dba0a6..4bf18112c7 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -111,7 +111,6 @@ class InterproceduralStringAnalysis( val fpe2SciMapping = state.interimFpe2sci.map { case (key, value) ⇒ key → ListBuffer(value) } - identity(fpe2SciMapping) if (state.computedLeanPath != null) { StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( state.computedLeanPath, fpe2SciMapping From 54ef3f9793bfbf7f6d4ca13735280259a158f428 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 12 Mar 2019 19:53:10 +0100 Subject: [PATCH 290/316] Improved the handling of interim results. --- .../InterproceduralTestMethods.java | 2 +- .../InterproceduralComputationState.scala | 28 +++++++++++++------ .../InterproceduralStringAnalysis.scala | 11 ++++---- ...InterproceduralInterpretationHandler.scala | 22 +++++++-------- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index d185a2bc1b..5eac05c5bd 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -534,7 +534,7 @@ public void fieldInitByConstructorParameter() { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "\\w" // Should be rather (\\w|value) + expectedStrings = "(\\w|value)" ) }) public String cyclicDependencyTest(String s) { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 9b2d2cd340..18e190f856 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -76,13 +76,10 @@ case class InterproceduralComputationState(entity: P) { val fpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() /** - * A mapping from a value / index of a FlatPathElement to StringConstancyInformation which is - * not yet final. For [[fpe2sci]] a list of [[StringConstancyInformation]] is necessary to - * compute (intermediate) results which might not be done in a single analysis step. For the - * interims, a single [[StringConstancyInformation]] element is sufficient, as it captures the - * results from [[fpe2sci]]. + * A mapping from a value / index of a FlatPathElement to StringConstancyInformation which are + * not yet final. */ - val interimFpe2sci: mutable.Map[Int, StringConstancyInformation] = mutable.Map() + val interimFpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() /** * An analysis may depend on the evaluation of its parameters. This number indicates how many @@ -162,10 +159,23 @@ case class InterproceduralComputationState(entity: P) { } /** - * Sets a value for the [[interimFpe2sci]] map. + * Appends a [[StringConstancyInformation]] element to [[interimFpe2sci]]. + * + * @param defSite The definition site to which append the given `sci` element for. + * @param sci The [[StringConstancyInformation]] to add to the list of interim results for the + * given definition site. */ - def setInterimFpe2Sci(defSite: Int, sci: StringConstancyInformation): Unit = - interimFpe2sci(defSite) = sci + def appendToInterimFpe2Sci( + defSite: Int, sci: StringConstancyInformation + ): Unit = { + if (!interimFpe2sci.contains(defSite)) { + interimFpe2sci(defSite) = ListBuffer() + } + // Append an element + if (!interimFpe2sci(defSite).contains(sci)) { + interimFpe2sci(defSite).append(sci) + } + } /** * Takes an entity as well as a definition site and append it to [[var2IndexMapping]]. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 4bf18112c7..b46e0ce762 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -108,12 +108,9 @@ class InterproceduralStringAnalysis( private def computeNewUpperBound( state: InterproceduralComputationState ): StringConstancyProperty = { - val fpe2SciMapping = state.interimFpe2sci.map { - case (key, value) ⇒ key → ListBuffer(value) - } if (state.computedLeanPath != null) { StringConstancyProperty(new PathTransformer(state.interimIHandler).pathToStringTree( - state.computedLeanPath, fpe2SciMapping + state.computedLeanPath, state.interimFpe2sci ).reduce(true)) } else { StringConstancyProperty.lb @@ -349,6 +346,10 @@ class InterproceduralStringAnalysis( eps match { case FinalEP(entity, p: StringConstancyProperty) ⇒ val e = entity.asInstanceOf[P] + // For updating the interim state + state.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i ⇒ + state.appendToInterimFpe2Sci(i, p.stringConstancyInformation) + } // If necessary, update the parameter information with which the // surrounding function / method of the entity was called with if (state.paramResultPositions.contains(e)) { @@ -399,7 +400,7 @@ class InterproceduralStringAnalysis( case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) ⇒ state.dependees = eps :: state.dependees state.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i ⇒ - state.setInterimFpe2Sci(i, ub.stringConstancyInformation) + state.appendToInterimFpe2Sci(i, ub.stringConstancyInformation) } getInterimResult(state) case _ ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 9c79cff109..dfd9f3bf05 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -84,14 +84,14 @@ class InterproceduralInterpretationHandler( // implicit parameter for "this" and for exceptions thrown outside the current function) if (defSite < 0 && (params.isEmpty || defSite == -1 || defSite <= ImmediateVMExceptionsOriginOffset)) { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.lb) return FinalEP(e, StringConstancyProperty.lb) } else if (defSite < 0) { val sci = getParam(params, defSite) - state.setInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(defSite, sci) return FinalEP(e, StringConstancyProperty(sci)) } else if (processedDefSites.contains(defSite)) { - state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) + state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) return FinalEP(e, StringConstancyProperty.getNeutralElement) } // Note that def sites referring to constant expressions will be deleted further down @@ -122,7 +122,7 @@ class InterproceduralInterpretationHandler( processVirtualMethodCall(vmc, defSite, callees) case nvmc: NonVirtualMethodCall[V] ⇒ processNonVirtualMethodCall(nvmc, defSite) case _ ⇒ - state.setInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) + state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.getNeutralElement) FinalEP(e, StringConstancyProperty.getNeutralElement) } } @@ -144,7 +144,7 @@ class InterproceduralInterpretationHandler( } val sci = finalEP.asFinal.p.stringConstancyInformation state.appendToFpe2Sci(defSite, sci) - state.setInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(defSite, sci) processedDefSites.remove(defSite) finalEP } @@ -164,7 +164,7 @@ class InterproceduralInterpretationHandler( processedDefSites.remove(defSite) StringConstancyInformation.lb } - state.setInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(defSite, sci) r } @@ -177,7 +177,7 @@ class InterproceduralInterpretationHandler( ) val sci = finalEP.asFinal.p.stringConstancyInformation state.appendToFpe2Sci(defSite, sci) - state.setInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(defSite, sci) finalEP } @@ -260,7 +260,7 @@ class InterproceduralInterpretationHandler( ): EOptionP[Entity, StringConstancyProperty] = { val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) val sci = result.asFinal.p.stringConstancyInformation - state.setInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(defSite, sci) state.appendToFpe2Sci(defSite, sci) result } @@ -321,10 +321,10 @@ class InterproceduralInterpretationHandler( ).interpret(nvmc, defSite) r match { case FinalEP(_, p: StringConstancyProperty) ⇒ - state.setInterimFpe2Sci(defSite, p.stringConstancyInformation) + state.appendToInterimFpe2Sci(defSite, p.stringConstancyInformation) state.appendToFpe2Sci(defSite, p.stringConstancyInformation) case _ ⇒ - state.setInterimFpe2Sci(defSite, StringConstancyInformation.lb) + state.appendToInterimFpe2Sci(defSite, StringConstancyInformation.lb) processedDefSites.remove(defSite) } r @@ -343,7 +343,7 @@ class InterproceduralInterpretationHandler( } else { StringConstancyInformation.lb } - state.setInterimFpe2Sci(defSite, sci) + state.appendToInterimFpe2Sci(defSite, sci) } /** From c5bd57ecb5aa305c3344e486ae90adbb0b20ab32 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 07:57:05 +0100 Subject: [PATCH 291/316] Had to refine how to update the part of the state that captures intermediate results to avoid endless loops that arise due to constantly changing upper bounds (for a discussion, see InterproceduralComputationState#appendToInterimFpe2Sci). --- .../InterproceduralComputationState.scala | 48 +++++++++++++++++-- .../InterproceduralStringAnalysis.scala | 7 ++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 18e190f856..114ea84c7c 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -81,6 +81,14 @@ case class InterproceduralComputationState(entity: P) { */ val interimFpe2sci: mutable.Map[Int, ListBuffer[StringConstancyInformation]] = mutable.Map() + /** + * Used by [[appendToInterimFpe2Sci]] to track for which entities a value was appended to + * [[interimFpe2sci]]. For a discussion of the necessity, see the documentation of + * [[interimFpe2sci]]. + */ + private val entity2lastInterimFpe2SciValue: mutable.Map[V, StringConstancyInformation] = + mutable.Map() + /** * An analysis may depend on the evaluation of its parameters. This number indicates how many * of such dependencies are still to be computed. @@ -159,21 +167,55 @@ case class InterproceduralComputationState(entity: P) { } /** - * Appends a [[StringConstancyInformation]] element to [[interimFpe2sci]]. + * Appends a [[StringConstancyInformation]] element to [[interimFpe2sci]]. The rules for + * appending are as follows: + *

      + *
    • If no element has been added to the interim result list belonging to `defSite`, the + * element is guaranteed to be added.
    • + *
    • If no entity is given, i.e., `None`, and the list at `defSite` does not contain + * `sci`, `sci` is guaranteed to be added. If necessary, the oldest element in the list + * belonging to `defSite` is removed.
    • + *
    • If a non-empty entity is given, it is checked whether an entry for that element has + * been added before by making use of [[entity2lastInterimFpe2SciValue]]. If so, the list is + * updated only if that element equals [[StringConstancyInformation.lb]]. The reason being + * is that otherwise the result of updating the upper bound might always produce a new + * result which would not make the analysis terminate. Basically, it might happen that the + * analysis produces for an entity ''e_1'' the result "(e1|e2)" which the analysis of + * entity ''e_2'' uses to update its state to "((e1|e2)|e3)". The analysis of ''e_1'', which + * depends on ''e_2'' and vice versa, will update its state producing "((e1|e2)|e3)" which + * makes the analysis of ''e_2'' update its to (((e1|e2)|e3)|e3) and so on.
    • + *
    * * @param defSite The definition site to which append the given `sci` element for. * @param sci The [[StringConstancyInformation]] to add to the list of interim results for the * given definition site. + * @param entity Optional. The entity for which the `sci` element was computed. */ def appendToInterimFpe2Sci( - defSite: Int, sci: StringConstancyInformation + defSite: Int, sci: StringConstancyInformation, entity: Option[V] = None ): Unit = { + val numElements = var2IndexMapping.values.flatten.count(_ == defSite) + var addedNewList = false if (!interimFpe2sci.contains(defSite)) { interimFpe2sci(defSite) = ListBuffer() + addedNewList = true } // Append an element - if (!interimFpe2sci(defSite).contains(sci)) { + val containsSci = interimFpe2sci(defSite).contains(sci) + if (!containsSci && entity.isEmpty) { + if (!addedNewList && interimFpe2sci(defSite).length == numElements) { + interimFpe2sci(defSite).remove(0) + } interimFpe2sci(defSite).append(sci) + } else if (!containsSci && entity.nonEmpty) { + if (!entity2lastInterimFpe2SciValue.contains(entity.get) || + entity2lastInterimFpe2SciValue(entity.get) == StringConstancyInformation.lb) { + entity2lastInterimFpe2SciValue(entity.get) = sci + if (interimFpe2sci(defSite).nonEmpty) { + interimFpe2sci(defSite).remove(0) + } + interimFpe2sci(defSite).append(sci) + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index b46e0ce762..9f1922dfc1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -399,8 +399,11 @@ class InterproceduralStringAnalysis( } case InterimLUBP(_: StringConstancyProperty, ub: StringConstancyProperty) ⇒ state.dependees = eps :: state.dependees - state.var2IndexMapping(eps.e.asInstanceOf[P]._1).foreach { i ⇒ - state.appendToInterimFpe2Sci(i, ub.stringConstancyInformation) + val uvar = eps.e.asInstanceOf[P]._1 + state.var2IndexMapping(uvar).foreach { i ⇒ + state.appendToInterimFpe2Sci( + i, ub.stringConstancyInformation, Some(uvar) + ) } getInterimResult(state) case _ ⇒ From 735d5fbbd134a4e14fcf91c1b2ec8e7027f8eb68 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 08:24:06 +0100 Subject: [PATCH 292/316] Added a question / todo. --- .../string_analysis/InterproceduralStringAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 9f1922dfc1..fc76c18bc2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -87,7 +87,7 @@ class InterproceduralStringAnalysis( * up to which number of callers parameter information are gathered. For "number of callers * greater than [[callersThreshold]]", parameters are approximated with the lower bound. */ - private val callersThreshold = 10 + private val callersThreshold = 10 // TODO: Is it possible to make this parameter configurable from the outise? private val declaredMethods = project.get(DeclaredMethodsKey) private final val fieldAccessInformation = project.get(FieldAccessInformationKey) From 24376477624449e7c33d5e3082efa10c1d06794d Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 13:28:35 +0100 Subject: [PATCH 293/316] Made the procedure more robust. --- .../InterproceduralInterpretationHandler.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index dfd9f3bf05..03f435f816 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -354,8 +354,12 @@ class InterproceduralInterpretationHandler( params: Seq[Seq[StringConstancyInformation]], defSite: Int ): StringConstancyInformation = { val paramPos = Math.abs(defSite + 2) - val paramScis = params.map(_(paramPos)).distinct - StringConstancyInformation.reduceMultiple(paramScis) + if (params.exists(_.length <= paramPos)) { + StringConstancyInformation.lb + } else { + val paramScis = params.map(_(paramPos)).distinct + StringConstancyInformation.reduceMultiple(paramScis) + } } /** From 87908a9cc40095e8325167f598cd07a913ea6dfd Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 17:10:00 +0100 Subject: [PATCH 294/316] Committed the state of this analysis which is actually used to analyze the JDK for the evaluation. --- .../info/StringAnalysisReflectiveCalls.scala | 213 +++++++++++++----- 1 file changed, 151 insertions(+), 62 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index 5c437e662f..bb1ea53917 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -9,7 +9,14 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimELUBP +import org.opalj.fpcf.InterimLUBP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEPS +import org.opalj.value.ValueInformation import org.opalj.br.analyses.BasicReport import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project @@ -23,16 +30,35 @@ import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.FPCFAnalysesManagerKey -import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.fpcf.cg.properties.ReflectionRelatedCallees +import org.opalj.br.fpcf.cg.properties.SerializationRelatedCallees +import org.opalj.br.fpcf.cg.properties.StandardInvokeCallees +import org.opalj.br.fpcf.cg.properties.ThreadRelatedIncompleteCallSites +import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt -import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall -import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.P import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.TACAITransformer +import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.DUVar +import org.opalj.tac.Stmt +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler +import org.opalj.tac.fpcf.analyses.cg.TriggeredStaticInitializerAnalysis +import org.opalj.tac.fpcf.analyses.cg.reflection.TriggeredReflectionRelatedCallsAnalysis +import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler +import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis +import org.opalj.tac.fpcf.analyses.cg.TriggeredThreadRelatedCallsAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis /** * Analyzes a project for calls provided by the Java Reflection API and tries to determine which @@ -67,10 +93,6 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { * analysis. The string are supposed to have the format as produced by [[buildFQMethodName]]. */ private val relevantMethodNames = List( - // The following is for the Java Reflection API - "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", - "java.lang.Class#getField", "java.lang.Class#getDeclaredField", - "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" // The following is for the javax.crypto API //"javax.crypto.Cipher#getInstance", "javax.crypto.Cipher#getMaxAllowedKeyLength", //"javax.crypto.Cipher#getMaxAllowedParameterSpec", "javax.crypto.Cipher#unwrap", @@ -79,18 +101,25 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { //"javax.crypto.ExemptionMechanism#getInstance", "javax.crypto.KeyAgreement#getInstance", //"javax.crypto.KeyGenerator#getInstance", "javax.crypto.Mac#getInstance", //"javax.crypto.SealedObject#getObject", "javax.crypto.SecretKeyFactory#getInstance" + // The following is for the Java Reflection API + "java.lang.Class#forName", "java.lang.ClassLoader#loadClass", + "java.lang.Class#getField", "java.lang.Class#getDeclaredField", + "java.lang.Class#getMethod", "java.lang.Class#getDeclaredMethod" ) /** - * A list of fully-qualified method names that are to be skipped, e.g., because they make the - * analysis crash. + * A list of fully-qualified method names that are to be skipped, e.g., because they make an + * analysis crash (e.g., com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize) */ - private val ignoreMethods = List( - // For the next one, there should be a \w inside the second string - // "com/sun/glass/ui/monocle/NativePlatformFactory#getNativePlatform", - // Check this result: - //"com/sun/jmx/mbeanserver/MBeanInstantiator#deserialize" - ) + private val ignoreMethods = List() + + // executeFrom specifies the index / counter when to start feeding entities to the property + // store. executeTo specifies the index / counter when to stop feeding entities to the property + // store. These values are basically to help debugging. executionCounter is a helper variable + // for that purpose + private val executeFrom = 0 + private val executeTo = 10000 + private var executionCounter = 0 override def title: String = "String Analysis for Reflective Calls" @@ -146,22 +175,24 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { if (isRelevantCall(call.declaringClass, call.name)) { val fqnMethodName = s"${method.classFile.thisType.fqn}#${method.name}" if (!ignoreMethods.contains(fqnMethodName)) { - //println( - // s"Processing ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" - //) - // Loop through all parameters and start the analysis for those that take a string - call.descriptor.parameterTypes.zipWithIndex.foreach { - case (ft, index) ⇒ - if (ft.toJava == "java.lang.String") { - val duvar = call.params(index).asVar - val e = (duvar, method) - - ps.force(e, StringConstancyProperty.key) - entityContext.append( - (e, buildFQMethodName(call.declaringClass, call.name)) - ) - } + if (executionCounter >= executeFrom && executionCounter <= executeTo) { + println( + s"Starting ${call.name} in ${method.classFile.thisType.fqn}#${method.name}" + ) + // Loop through all parameters and start the analysis for those that take a string + call.descriptor.parameterTypes.zipWithIndex.foreach { + case (ft, index) ⇒ + if (ft.toJava == "java.lang.String") { + val duvar = call.params(index).asVar + val e = (duvar, method) + ps.force(e, StringConstancyProperty.key) + entityContext.append( + (e, buildFQMethodName(call.declaringClass, call.name)) + ) + } + } } + executionCounter += 1 } } } @@ -190,58 +221,116 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { BasicReport(report) } + private def processStatements( + ps: PropertyStore, + stmts: Array[Stmt[V]], + m: Method, + resultMap: ResultMapType + ): Unit = { + stmts.foreach { stmt ⇒ + // Using the following switch speeds up the whole process + (stmt.astID: @switch) match { + case Assignment.ASTID ⇒ stmt match { + case Assignment(_, _, c: StaticFunctionCall[V]) ⇒ + processFunctionCall(ps, m, c, resultMap) + case Assignment(_, _, c: VirtualFunctionCall[V]) ⇒ + processFunctionCall(ps, m, c, resultMap) + case _ ⇒ + } + case ExprStmt.ASTID ⇒ stmt match { + case ExprStmt(_, c: StaticFunctionCall[V]) ⇒ + processFunctionCall(ps, m, c, resultMap) + case ExprStmt(_, c: VirtualFunctionCall[V]) ⇒ + processFunctionCall(ps, m, c, resultMap) + case _ ⇒ + } + case _ ⇒ + } + } + } + + private def continuation( + ps: PropertyStore, m: Method, resultMap: ResultMapType + )(eps: SomeEPS): ProperPropertyComputationResult = { + eps match { + case FinalP(tac: TACAI) ⇒ + processStatements(ps, tac.tac.get.stmts, m, resultMap) + Result(m, tac) + case InterimLUBP(lb, ub) ⇒ + InterimResult( + m, lb, ub, List(eps), continuation(ps, m, resultMap) + ) + case _ ⇒ throw new IllegalStateException("should never happen!") + } + } + override def doAnalyze( project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean ): ReportableAnalysisResult = { - val t0 = System.currentTimeMillis() - - implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) - project.get(FPCFAnalysesManagerKey).runAll(LazyIntraproceduralStringAnalysis) - val tacProvider = project.get(SimpleTACAIKey) + val manager = project.get(FPCFAnalysesManagerKey) + implicit val (propertyStore, analyses) = manager.runAll( + TACAITransformer, + LazyL0BaseAIAnalysis, + RTACallGraphAnalysisScheduler, + TriggeredStaticInitializerAnalysis, + TriggeredLoadedClassesAnalysis, + TriggeredFinalizerAnalysisScheduler, + TriggeredThreadRelatedCallsAnalysis, + TriggeredSerializationRelatedCallsAnalysis, + TriggeredReflectionRelatedCallsAnalysis, + TriggeredSystemPropertiesAnalysis, + TriggeredInstantiatedTypesAnalysis, + LazyCalleesAnalysis(Set( + StandardInvokeCallees, + SerializationRelatedCallees, + ReflectionRelatedCallees, + ThreadRelatedIncompleteCallSites + )), + LazyInterproceduralStringAnalysis + // LazyIntraproceduralStringAnalysis + ) // Stores the obtained results for each supported reflective operation val resultMap: ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]]() relevantMethodNames.foreach { resultMap(_) = ListBuffer() } project.allMethodsWithBody.foreach { m ⇒ - // To dramatically reduce the work of the tacProvider, quickly check if a method is - // relevant at all + // To dramatically reduce work, quickly check if a method is relevant at all if (instructionsContainRelevantMethod(m.body.get.instructions)) { - val stmts = tacProvider(m).stmts - stmts.foreach { stmt ⇒ - // Use the following switch to speed-up the whole process - (stmt.astID: @switch) match { - case Assignment.ASTID ⇒ stmt match { - case Assignment(_, _, c: StaticFunctionCall[V]) ⇒ - processFunctionCall(propertyStore, m, c, resultMap) - case Assignment(_, _, c: VirtualFunctionCall[V]) ⇒ - processFunctionCall(propertyStore, m, c, resultMap) - case _ ⇒ - } - case ExprStmt.ASTID ⇒ stmt match { - case ExprStmt(_, c: StaticFunctionCall[V]) ⇒ - processFunctionCall(propertyStore, m, c, resultMap) - case ExprStmt(_, c: VirtualFunctionCall[V]) ⇒ - processFunctionCall(propertyStore, m, c, resultMap) - case _ ⇒ - } - case _ ⇒ + var tac: TACode[TACMethodParameter, DUVar[ValueInformation]] = null + val tacaiEOptP = propertyStore(m, TACAI.key) + if (tacaiEOptP.hasUBP) { + if (tacaiEOptP.ub.tac.isEmpty) { + // No TAC available, e.g., because the method has no body + println(s"No body for method: ${m.classFile.fqn}#${m.name}") + } else { + tac = tacaiEOptP.ub.tac.get + processStatements(propertyStore, tac.stmts, m, resultMap) } + } else { + InterimResult( + m, + StringConstancyProperty.ub, + StringConstancyProperty.lb, + List(tacaiEOptP), + continuation(propertyStore, m, resultMap) + ) } } } - // TODO: The call to waitOnPhaseCompletion is not 100 % correct, however, without it - // resultMap does not get filled at all + val t0 = System.currentTimeMillis() propertyStore.waitOnPhaseCompletion() entityContext.foreach { case (e, callName) ⇒ propertyStore.properties(e).toIndexedSeq.foreach { - case FinalP(p) ⇒ - resultMap(callName).append( - p.asInstanceOf[StringConstancyProperty].stringConstancyInformation - ) + case FinalP(p: StringConstancyProperty) ⇒ + resultMap(callName).append(p.stringConstancyInformation) + case InterimELUBP(_, _, ub: StringConstancyProperty) ⇒ + resultMap(callName).append(ub.stringConstancyInformation) case _ ⇒ + println(s"Neither a final nor an interim result for $e in $callName; "+ + "this should never be the case!") } } From a68c6498cd7e0414f73f4a576aed1b501c9f4c04 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 13 Mar 2019 19:46:24 +0100 Subject: [PATCH 295/316] Added support for static field read operations. --- .../InterproceduralTestMethods.java | 18 +++++++- .../InterproceduralFieldInterpreter.scala | 39 +++++++++++------ .../InterproceduralGetStaticInterpreter.scala | 42 ------------------- ...InterproceduralInterpretationHandler.scala | 28 ++++--------- .../finalizer/GetFieldFinalizer.scala | 6 +-- 5 files changed, 53 insertions(+), 80 deletions(-) delete mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 5eac05c5bd..9de42d75b8 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -41,6 +41,8 @@ public class InterproceduralTestMethods { private float secretNumber; + public static String someKey = "will not be revealed here"; + /** * {@see LocalTestMethods#analyzeString} */ @@ -269,8 +271,8 @@ public void contextInsensitivityTest() { + "is involved", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, - expectedStrings = "\\w" + expectedLevel = PARTIALLY_CONSTANT, + expectedStrings = "\\wImpl_Stub" ) }) @@ -652,6 +654,18 @@ public void valueOfTest() { analyzeString(String.valueOf(getRuntimeClassName())); } + @StringDefinitionsCollection( + value = "a case where a static property is read", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "will not be revealed here" + ) + }) + public void getStaticFieldTest() { + analyzeString(someKey); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 16fbfbef1d..dcc5efb365 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -12,17 +12,19 @@ import org.opalj.br.analyses.FieldAccessInformation import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel -import org.opalj.tac.GetField import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralStringAnalysis +import org.opalj.tac.FieldRead +import org.opalj.tac.PutField +import org.opalj.tac.PutStatic +import org.opalj.tac.Stmt /** - * The `InterproceduralFieldInterpreter` is responsible for processing [[GetField]]s. In this - * implementation, there is currently only primitive support for fields, i.e., they are not analyzed - * but a constant [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation]] - * is returned (see [[interpret]] of this class). + * The `InterproceduralFieldInterpreter` is responsible for processing instances of [[FieldRead]]s. + * At this moment, this includes instances of [[PutField]] and [[PutStatic]]. For the processing + * procedure, see [[InterproceduralFieldInterpreter#interpret]]. * * @see [[AbstractStringInterpreter]] * @@ -35,14 +37,15 @@ class InterproceduralFieldInterpreter( fieldAccessInformation: FieldAccessInformation, ) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { - override type T = GetField[V] + override type T = FieldRead[V] /** - * Currently, fields are not interpreted. Thus, this function always returns a list with a - * single element consisting of - * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel.DYNAMIC]], - * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyType.APPEND]] and - * [[org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation.UnknownWordSymbol]]. + * Currently, fields are approximated using the following approach. If a field of a type not + * supported by the [[InterproceduralStringAnalysis]] is passed, + * [[StringConstancyInformation.lb]] will be produces. Otherwise, all write accesses are + * considered and analyzed. If a field is not initialized within a constructor or the class + * itself, it will be approximated using all write accesses as well as with the lower bound and + * "null" => in these cases fields are [[StringConstancyLevel.DYNAMIC]]. * * @note For this implementation, `defSite` plays a role! * @@ -59,7 +62,7 @@ class InterproceduralFieldInterpreter( val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { case (m, pcs) ⇒ pcs.foreach { pc ⇒ - if (m.name == "") { + if (m.name == "" || m.name == "") { hasInit = true } val (tacEps, tac) = getTACAI(ps, m, state) @@ -69,7 +72,7 @@ class InterproceduralFieldInterpreter( tac match { case Some(methodTac) ⇒ val stmt = methodTac.stmts(methodTac.pcToIndex(pc)) - val entity = (stmt.asPutField.value.asVar, m) + val entity = (extractUVarFromPut(stmt), m) val eps = ps(entity, StringConstancyProperty.key) if (eps.isRefinable) { state.dependees = eps :: state.dependees @@ -124,4 +127,14 @@ class InterproceduralFieldInterpreter( } } + /** + * This function extracts a DUVar from a given statement which is required to be either of type + * [[PutStatic]] or [[PutField]]. + */ + private def extractUVarFromPut(field: Stmt[V]): V = field match { + case PutStatic(_, _, _, _, value) ⇒ value.asVar + case PutField(_, _, _, _, _, value) ⇒ value.asVar + case _ ⇒ throw new IllegalArgumentException(s"Type of $field is currently not supported!") + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala deleted file mode 100644 index dff867c7e4..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralGetStaticInterpreter.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural - -import org.opalj.fpcf.Entity -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.FinalEP -import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.properties.StringConstancyProperty -import org.opalj.tac.GetStatic -import org.opalj.tac.Stmt -import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter - -/** - * The `InterproceduralGetStaticInterpreter` is responsible for processing - * [[org.opalj.tac.GetStatic]]s in an interprocedural fashion. - * - * @see [[AbstractStringInterpreter]] - * - * @author Patrick Mell - */ -class InterproceduralGetStaticInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], - exprHandler: InterproceduralInterpretationHandler -) extends AbstractStringInterpreter(cfg, exprHandler) { - - override type T = GetStatic - - /** - * Currently, this type is not interpreted. Thus, this function always returns a result - * containing [[StringConstancyProperty.lb]]. - * - * @note For this implementation, `defSite` does currently not play a role! - * - * @see [[AbstractStringInterpreter.interpret]] - */ - override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = - // TODO: Approximate in a better way - FinalEP(instr, StringConstancyProperty.lb) - -} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 03f435f816..2739d88cc2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -48,6 +48,7 @@ import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.GetFieldFinalizer import org.opalj.tac.SimpleValueConst import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.StaticFunctionCallFinalizer +import org.opalj.tac.FieldRead /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -106,8 +107,8 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: ArrayLoad[V]) ⇒ processArrayLoad(expr, defSite, params) case Assignment(_, _, expr: New) ⇒ processNew(expr, defSite) - case Assignment(_, _, expr: GetStatic) ⇒ processGetStatic(expr, defSite) - case ExprStmt(_, expr: GetStatic) ⇒ processGetStatic(expr, defSite) + case Assignment(_, _, expr: GetStatic) ⇒ processGetField(expr, defSite) + case ExprStmt(_, expr: GetStatic) ⇒ processGetField(expr, defSite) case Assignment(_, _, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case ExprStmt(_, expr: VirtualFunctionCall[V]) ⇒ processVFC(expr, defSite, params) case Assignment(_, _, expr: StaticFunctionCall[V]) ⇒ @@ -181,19 +182,6 @@ class InterproceduralInterpretationHandler( finalEP } - /** - * Helper / utility function for processing [[GetStatic]]s. - */ - private def processGetStatic( - expr: GetStatic, defSite: Int - ): EOptionP[Entity, StringConstancyProperty] = { - val result = new InterproceduralGetStaticInterpreter(cfg, this).interpret( - expr, defSite - ) - doInterimResultHandling(result, defSite) - result - } - /** * Helper / utility function for interpreting [[VirtualFunctionCall]]s. */ @@ -269,7 +257,7 @@ class InterproceduralInterpretationHandler( * Helper / utility function for processing [[GetField]]s. */ private def processGetField( - expr: GetField[V], defSite: Int + expr: FieldRead[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { val r = new InterproceduralFieldInterpreter( state, this, ps, fieldAccessInformation @@ -380,10 +368,10 @@ class InterproceduralInterpretationHandler( VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) - case Assignment(_, _, gf: GetField[V]) ⇒ - GetFieldFinalizer(state).finalizeInterpretation(gf, defSite) - case ExprStmt(_, gf: GetField[V]) ⇒ - GetFieldFinalizer(state).finalizeInterpretation(gf, defSite) + case Assignment(_, _, fr: FieldRead[V]) ⇒ + GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) + case ExprStmt(_, fr: FieldRead[V]) ⇒ + GetFieldFinalizer(state).finalizeInterpretation(fr, defSite) case Assignment(_, _, sfc: StaticFunctionCall[V]) ⇒ StaticFunctionCallFinalizer(state).finalizeInterpretation(sfc, defSite) case ExprStmt(_, sfc: StaticFunctionCall[V]) ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala index 56f11f0beb..0f6ce3a2b1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/GetFieldFinalizer.scala @@ -3,16 +3,16 @@ package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedur import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.GetField +import org.opalj.tac.FieldRead class GetFieldFinalizer( state: InterproceduralComputationState ) extends AbstractFinalizer(state) { - override protected type T = GetField[V] + override protected type T = FieldRead[V] /** - * Finalizes [[GetField]]s. + * Finalizes [[FieldRead]]s. *

    * @inheritdoc */ From cb0b6b7eb887cc731491c760521e6fb6e80243b0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 14 Mar 2019 19:11:52 +0100 Subject: [PATCH 296/316] Changed the symbol, which is used to indicate that a (sub) string can be any string, from "\w" to ".*" as this complies with the regular expression semantics. --- .../InterproceduralTestMethods.java | 26 ++++----- .../string_analysis/LocalTestMethods.java | 58 +++++++++---------- .../StringConstancyInformation.scala | 2 +- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index 9de42d75b8..d3b5be4599 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -114,7 +114,7 @@ public void fromStaticMethodWithParamTest() { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "\\w" + expectedStrings = ".*" ), }) @@ -128,7 +128,7 @@ public void staticMethodOutOfScopeTest() throws FileNotFoundException { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(\\w)*" + expectedStrings = "(.*)*" ) }) @@ -147,8 +147,8 @@ public void methodOutOfScopeTest() throws FileNotFoundException { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(java.lang.Object|\\w|java.lang.(Integer|" - + "Object|Runtime)|\\w)" + expectedStrings = "(java.lang.Object|.*|java.lang.(Integer|" + + "Object|Runtime)|.*)" ) }) @@ -188,7 +188,7 @@ public void appendTest0(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "classname:StringBuilder,osname:\\w" + expectedStrings = "classname:StringBuilder,osname:.*" ) }) @@ -272,7 +272,7 @@ public void contextInsensitivityTest() { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "\\wImpl_Stub" + expectedStrings = ".*Impl_Stub" ) }) @@ -325,7 +325,7 @@ public void appendWithTwoDefSitesWithFuncCallTest(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "get(\\w|Hello, Worldjava.lang.Runtime)" + expectedStrings = "get(.*|Hello, Worldjava.lang.Runtime)" ) }) @@ -435,7 +435,7 @@ public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alpha stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(\\w\\w_tie|\\w_tie)" + expectedStrings = "(.*.*_tie|.*_tie)" ) }) public String tieName(String var0, Remote remote) { @@ -476,7 +476,7 @@ private void belongsToFieldReadTest() { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(^null$|\\w)" + expectedStrings = "(^null$|.*)" ) }) public void fieldWithNoWriteTest() { @@ -488,7 +488,7 @@ public void fieldWithNoWriteTest() { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "\\w" + expectedStrings = ".*" ) }) public void nonSupportedFieldTypeRead() { @@ -536,7 +536,7 @@ public void fieldInitByConstructorParameter() { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "(\\w|value)" + expectedStrings = "(.*|value)" ) }) public String cyclicDependencyTest(String s) { @@ -606,11 +606,11 @@ private static String severalReturnValuesStaticFunction(int i) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "\\w" + expectedStrings = ".*" ), @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "\\w" + expectedStrings = ".*" ) }) public void functionWithNoReturnValueTest1() { diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java index f3bb4363bd..30c2c23530 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/LocalTestMethods.java @@ -134,7 +134,7 @@ public void directAppendConcats() { value = "at this point, function call cannot be handled => DYNAMIC", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = DYNAMIC, expectedStrings = ".*" ) }) public void fromFunctionCall() { @@ -146,7 +146,7 @@ public void fromFunctionCall() { value = "constant string + string from function call => PARTIALLY_CONSTANT", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "java.lang.\\w" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "java.lang..*" ) }) public void fromConstantAndFunctionCall() { @@ -198,8 +198,8 @@ public void multipleConstantDefSites(boolean cond) { stringDefinitions = { @StringDefinitions( expectedLevel = DYNAMIC, - expectedStrings = "((java.lang.Object|\\w)|java.lang.System|" - + "java.lang.\\w|\\w)" + expectedStrings = "((java.lang.Object|.*)|java.lang.System|" + + "java.lang..*|.*)" ) }) public void multipleDefSites(int value) { @@ -497,7 +497,7 @@ public void whileWithBreak(int i) { ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(iv1|iv2): ((great!)?)*(\\w)?" + expectedStrings = "(iv1|iv2): ((great!)?)*(.*)?" ) }) public void extensive(boolean cond) { @@ -530,7 +530,7 @@ public void extensive(boolean cond) { value = "an example with a throw (and no try-catch-finally)", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:\\w" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "File Content:.*" ) }) public void withThrow(String filename) throws IOException { @@ -548,15 +548,15 @@ public void withThrow(String filename) throws IOException { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "File Content:(\\w)?" + expectedStrings = "File Content:(.*)?" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "File Content:(\\w)?" + expectedStrings = "File Content:(.*)?" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "File Content:(\\w)?" + expectedStrings = "File Content:(.*)?" ) }) public void withException(String filename) { @@ -574,13 +574,13 @@ public void withException(String filename) { value = "case with a try-catch-finally exception", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(\\w|=====)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "=====(.*|=====)" ) }) public void tryCatchFinally(String filename) { @@ -603,13 +603,13 @@ public void tryCatchFinally(String filename) { // "EOS" can not be found for the first case (the difference to the case // tryCatchFinally is that a second CatchNode is not present in the // throwable case) - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(\\w|:EOS)" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "BOS:(.*|:EOS)" ) }) public void tryCatchFinallyWithThrowable(String filename) { @@ -628,10 +628,10 @@ public void tryCatchFinallyWithThrowable(String filename) { value = "simple examples to clear a StringBuilder", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = DYNAMIC, expectedStrings = ".*" ), @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = DYNAMIC, expectedStrings = ".*" ) }) public void simpleClearExamples() { @@ -671,11 +671,11 @@ public void advancedClearExampleWithSetLength(int value) { value = "a simple and a little more advanced example with a StringBuilder#replace call", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = DYNAMIC, expectedStrings = ".*" ), @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(init_value:Hello, world!Goodbye|\\wGoodbye)" + expectedStrings = "(init_value:Hello, world!Goodbye|.*Goodbye)" ) }) public void replaceExamples(int value) { @@ -705,7 +705,7 @@ public void replaceExamples(int value) { expectedLevel = CONSTANT, expectedStrings = "" ), @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "((\\w)?)*" + expectedLevel = DYNAMIC, expectedStrings = "((.*)?)*" ) }) public void breakContinueExamples(int value) { @@ -810,7 +810,7 @@ public void secondStringBuilderRead(String className) { value = "an example that uses a non final field", stringDefinitions = { @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "Field Value:\\w" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "Field Value:.*" ) }) public void nonFinalFieldRead() { @@ -863,16 +863,16 @@ public void crissCrossExample(String className) { value = "examples that use a passed parameter to define strings that are analyzed", stringDefinitions = { @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = DYNAMIC, expectedStrings = ".*" ), @StringDefinitions( - expectedLevel = DYNAMIC, expectedStrings = "\\w" + expectedLevel = DYNAMIC, expectedStrings = ".*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=\\w" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*" ), @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=\\w\\w" + expectedLevel = PARTIALLY_CONSTANT, expectedStrings = "value=.*.*" ) }) public void parameterRead(String stringValue, StringBuilder sbValue) { @@ -895,7 +895,7 @@ public void parameterRead(String stringValue, StringBuilder sbValue) { stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(set\\w|s\\w)" + expectedStrings = "(set.*|s.*)" ), }) public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException { @@ -921,7 +921,7 @@ public void twoDefinitionsOneUsage(String getName) throws ClassNotFoundException stringDefinitions = { @StringDefinitions( expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "Hello: (\\w|\\w|\\w)?" + expectedStrings = "Hello: (.*|.*|.*)?" ), }) protected void setDebugFlags(String[] var1) { @@ -967,8 +967,8 @@ protected void setDebugFlags(String[] var1) { @StringDefinitionsCollection( value = "an example with an unknown character read", stringDefinitions = { - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "\\w"), - @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = "\\w"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), + @StringDefinitions(expectedLevel = DYNAMIC, expectedStrings = ".*"), }) public void unknownCharValue() { int charCode = new Random().nextInt(200); diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala index 61c77afdf6..d481043094 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringConstancyInformation.scala @@ -36,7 +36,7 @@ object StringConstancyInformation { * This string stores the value that is to be used when a string is dynamic, i.e., can have * arbitrary values. */ - val UnknownWordSymbol: String = "\\w" + val UnknownWordSymbol: String = ".*" /** * The stringified version of a (dynamic) integer value. From 670e38eba150f93b6ae34cfdddede6ec115295e8 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 14 Mar 2019 20:23:04 +0100 Subject: [PATCH 297/316] Fixed a typo. --- .../string_analysis/InterproceduralStringAnalysis.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index fc76c18bc2..e89595469d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -87,7 +87,7 @@ class InterproceduralStringAnalysis( * up to which number of callers parameter information are gathered. For "number of callers * greater than [[callersThreshold]]", parameters are approximated with the lower bound. */ - private val callersThreshold = 10 // TODO: Is it possible to make this parameter configurable from the outise? + private val callersThreshold = 10 // TODO: Is it possible to make this parameter configurable from the outside? private val declaredMethods = project.get(DeclaredMethodsKey) private final val fieldAccessInformation = project.get(FieldAccessInformationKey) From 5eacbf2925469db14e70401229bebaebecc8c6ce Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 09:25:13 +0100 Subject: [PATCH 298/316] The path finding procedure could run in an endless loop which is now prevented. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 1fe62a3768..22851efd5f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -204,9 +204,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // little bit more complicated to find the end of the "if": Go up to the element that points // to the if target element if (ifTarget < branchingSite) { + val seenElements: mutable.Map[Int, Unit] = mutable.Map() val toVisit = mutable.Stack[Int](branchingSite) while (toVisit.nonEmpty) { val popped = toVisit.pop() + seenElements(popped) = Unit val relevantSuccessors = cfg.bb(popped).successors.filter { _.isInstanceOf[BasicBlock] }.map(_.asBasicBlock) @@ -214,8 +216,8 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { endIndex = cfg.bb(popped).endPC toVisit.clear() } else { - toVisit.pushAll(relevantSuccessors.filter { - _.nodeId != ifTarget + toVisit.pushAll(relevantSuccessors.filter { s ⇒ + s.nodeId != ifTarget && !seenElements.contains(s.nodeId) }.map(_.startPC)) } } From 9b82d5a21a574b51da326a03ee8c69bfec91782d Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 09:40:23 +0100 Subject: [PATCH 299/316] Made this finalizer more robust. --- .../finalizer/VirtualFunctionCallFinalizer.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala index adaa8635c6..c41153dd3a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/VirtualFunctionCallFinalizer.scala @@ -66,9 +66,11 @@ class VirtualFunctionCallFinalizer( state.iHandler.finalizeDefSite(ds, state) } } - val appendSci = StringConstancyInformation.reduceMultiple( - paramDefSites.flatMap(state.fpe2sci(_)) - ) + val appendSci = if (paramDefSites.forall(state.fpe2sci.contains)) { + StringConstancyInformation.reduceMultiple( + paramDefSites.flatMap(state.fpe2sci(_)) + ) + } else StringConstancyInformation.lb val finalSci = if (receiverSci.isTheNeutralElement && appendSci.isTheNeutralElement) { receiverSci From 0973b3fd443a9f5fad3449cbfc7a4b77650e8e16 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 11:26:40 +0100 Subject: [PATCH 300/316] Made the path finding procedure more robust by avoiding -1 values if the end PC of a catch node is -1. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 22851efd5f..315480f9bb 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -122,7 +122,11 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { // If the conditional is encloses in a try-catch block, consider this // bounds and otherwise the bounds of the surrounding element cfg.bb(nextBlock).successors.find(_.isInstanceOf[CatchNode]) match { - case Some(cs: CatchNode) ⇒ endSite = cs.endPC + case Some(cs: CatchNode) ⇒ + endSite = cs.endPC + if (endSite == -1) { + endSite = nextBlock + } case _ ⇒ endSite = if (nextBlock > branchingSite) nextBlock - 1 else cfg.findNaturalLoops().find { From e6735b51195ae52010c38dc3097597aadac505e7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 11:48:24 +0100 Subject: [PATCH 301/316] Made the reduce accumulator more robust. --- .../br/fpcf/properties/string_definition/StringTree.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala index f3a34a8915..99e73e9f15 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/string_definition/StringTree.scala @@ -140,7 +140,9 @@ sealed abstract class StringTreeElement(val children: ListBuffer[StringTreeEleme case StringTreeRepetition(c, lowerBound, upperBound) ⇒ val times = if (lowerBound.isDefined && upperBound.isDefined) (upperBound.get - lowerBound.get).toString else InfiniteRepetitionSymbol - val reduced = reduceAcc(c).head + val reducedAcc = reduceAcc(c) + val reduced = if (reducedAcc.nonEmpty) reducedAcc.head else + StringConstancyInformation.lb List(StringConstancyInformation( reduced.constancyLevel, reduced.constancyType, From 07fd19409dfcc3e633ec2055614e9ee09a27c2dc Mon Sep 17 00:00:00 2001 From: Patrick Date: Fri, 15 Mar 2019 13:36:14 +0100 Subject: [PATCH 302/316] Made the path finding procedure more robust by avoiding that the end side can be smaller than the start side. --- .../string_analysis/preprocessing/AbstractPathFinder.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala index 315480f9bb..a8d094f7ce 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/AbstractPathFinder.scala @@ -153,6 +153,9 @@ abstract class AbstractPathFinder(cfg: CFG[Stmt[V], TACStmts[V]]) { } } } + if (endSite < branchingSite) { + endSite = nextBlock + } } (branchingSite, endSite) From d02f209cd71baf192d9f485be9016c578aec7208 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 17 Mar 2019 19:37:22 +0100 Subject: [PATCH 303/316] Extended the analysis to make the number of field write accesses configurable, i.e., a threshold when to approximate field reads as the lower bound. --- .../InterproceduralComputationState.scala | 4 +++- .../InterproceduralStringAnalysis.scala | 16 ++++++++++++++-- .../InterproceduralFieldInterpreter.scala | 10 ++++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 114ea84c7c..93676015d2 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -26,8 +26,10 @@ import org.opalj.tac.VirtualFunctionCall * have all required information ready for a final result. * * @param entity The entity for which the analysis was started with. + * @param fieldWriteThreshold See the documentation of + * [[InterproceduralStringAnalysis#fieldWriteThreshold]]. */ -case class InterproceduralComputationState(entity: P) { +case class InterproceduralComputationState(entity: P, fieldWriteThreshold: Int = 100) { /** * The Three-Address Code of the entity's method */ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index e89595469d..b0d9c0885b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -81,13 +81,25 @@ class InterproceduralStringAnalysis( val project: SomeProject ) extends FPCFAnalysis { + // TODO: Is it possible to make the following two parameters configurable from the outside? + /** * To analyze an expression within a method ''m'', callers information might be necessary, e.g., * to know with which arguments ''m'' is called. [[callersThreshold]] determines the threshold * up to which number of callers parameter information are gathered. For "number of callers * greater than [[callersThreshold]]", parameters are approximated with the lower bound. */ - private val callersThreshold = 10 // TODO: Is it possible to make this parameter configurable from the outside? + private val callersThreshold = 10 + + /** + * To analyze a read operation of field, ''f'', all write accesses, ''wa_f'', to ''f'' have to + * be analyzed. ''fieldWriteThreshold'' determines the threshold of ''|wa_f|'' when ''f'' is to + * be approximated as the lower bound, i.e., ''|wa_f|'' is greater than ''fieldWriteThreshold'' + * then the read operation of ''f'' is approximated as the lower bound. Otherwise, if ''|wa_f|'' + * is less or equal than ''fieldWriteThreshold'', analyze all ''wa_f'' to approximate the read + * of ''f''. + */ + private val fieldWriteThreshold = 100 private val declaredMethods = project.get(DeclaredMethodsKey) private final val fieldAccessInformation = project.get(FieldAccessInformationKey) @@ -122,7 +134,7 @@ class InterproceduralStringAnalysis( ): StringConstancyProperty = StringConstancyProperty.lb def analyze(data: P): ProperPropertyComputationResult = { - val state = InterproceduralComputationState(data) + val state = InterproceduralComputationState(data, fieldWriteThreshold) val dm = declaredMethods(data._2) val tacaiEOptP = ps(data._2, TACAI.key) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index dcc5efb365..789a6c1676 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -53,14 +53,20 @@ class InterproceduralFieldInterpreter( */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { val defSitEntity: Integer = defSite + // Unknown type => Cannot further approximate if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { - // Unknown type => Cannot further approximate + return FinalEP(instr, StringConstancyProperty.lb) + } + // No write accesses or the number of write accesses exceeds the threshold => approximate + // with lower bound + val writeAccesses = fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name) + if (writeAccesses.isEmpty || writeAccesses.length > state.fieldWriteThreshold) { return FinalEP(instr, StringConstancyProperty.lb) } var hasInit = false val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() - fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name).foreach { + writeAccesses.foreach { case (m, pcs) ⇒ pcs.foreach { pc ⇒ if (m.name == "" || m.name == "") { hasInit = true From 8d6030bb6d3b4346f6091876834fa4d1e0445bee Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 18 Mar 2019 16:18:37 +0100 Subject: [PATCH 304/316] Added a comment / TODO. --- .../interprocedural/InterproceduralInterpretationHandler.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 2739d88cc2..68fc747542 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -246,6 +246,8 @@ class InterproceduralInterpretationHandler( private def processBinaryExpr( expr: BinaryExpr[V], defSite: Int ): EOptionP[Entity, StringConstancyProperty] = { + // TODO: For binary expressions, use the underlying domain to retrieve the result of such + // expressions val result = new BinaryExprInterpreter(cfg, this).interpret(expr, defSite) val sci = result.asFinal.p.stringConstancyInformation state.appendToInterimFpe2Sci(defSite, sci) From 0810be388b77160f128591ca0ba8ea278e0014c2 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 18 Mar 2019 16:40:38 +0100 Subject: [PATCH 305/316] Added a comment / TODO. --- .../interprocedural/InterproceduralFieldInterpreter.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index 789a6c1676..ef3b0c2a97 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -52,6 +52,9 @@ class InterproceduralFieldInterpreter( * @see [[AbstractStringInterpreter.interpret]] */ override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + // TODO: The approximation of fields might be outsourced into a dedicated analysis. Then, + // one could add a finer-grained processing or provide different abstraction levels. This + // String analysis could then use the field analysis. val defSitEntity: Integer = defSite // Unknown type => Cannot further approximate if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { From 43a35f208106dda5452062047610d81e989a494b Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 21 Mar 2019 10:50:26 +0100 Subject: [PATCH 306/316] When the field interpreter was extended to support the threshold, a little bug was introduced which lead to the fact that "^null$" would not be added to the possible strings if there was not field write access. This is now fixed. --- .../interprocedural/InterproceduralFieldInterpreter.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index ef3b0c2a97..e78b63c905 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -60,10 +60,9 @@ class InterproceduralFieldInterpreter( if (!InterproceduralStringAnalysis.isSupportedType(instr.declaredFieldType)) { return FinalEP(instr, StringConstancyProperty.lb) } - // No write accesses or the number of write accesses exceeds the threshold => approximate - // with lower bound + // Write accesses exceeds the threshold => approximate with lower bound val writeAccesses = fieldAccessInformation.writeAccesses(instr.declaringClass, instr.name) - if (writeAccesses.isEmpty || writeAccesses.length > state.fieldWriteThreshold) { + if (writeAccesses.length > state.fieldWriteThreshold) { return FinalEP(instr, StringConstancyProperty.lb) } From 92e0c5284ac0fa1072ed077c69709de6041447c9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 21 Mar 2019 11:27:44 +0100 Subject: [PATCH 307/316] Under some circumstances, the empty string was turned into the lower bound. This lead to one little refinement in the VirtualFunctionCallPreparationInterpreter. --- .../InterproceduralStringAnalysis.scala | 22 ++++++++++++++++--- ...alFunctionCallPreparationInterpreter.scala | 11 +++++++++- .../preprocessing/PathTransformer.scala | 8 +++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index b0d9c0885b..3320765b43 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -289,9 +289,25 @@ class InterproceduralStringAnalysis( if (attemptFinalResultComputation) { if (state.dependees.isEmpty && computeResultsForPath(state.computedLeanPath, state)) { - sci = new PathTransformer(state.iHandler).pathToStringTree( - state.computedLeanPath, state.fpe2sci - ).reduce(true) + // Check whether we deal with the empty string; it requires special treatment as the + // PathTransformer#pathToStringTree would not handle it correctly (as + // PathTransformer#pathToStringTree is involved in a mutual recursion) + val isEmptyString = if (state.computedLeanPath.elements.length == 1) { + state.computedLeanPath.elements.head match { + case FlatPathElement(i) ⇒ + state.fpe2sci.contains(i) && state.fpe2sci(i).length == 1 && + state.fpe2sci(i).head == StringConstancyInformation.getNeutralElement + case _ ⇒ false + } + } else false + + sci = if (isEmptyString) { + StringConstancyInformation.getNeutralElement + } else { + new PathTransformer(state.iHandler).pathToStringTree( + state.computedLeanPath, state.fpe2sci + ).reduce(true) + } } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index b289becd91..87a195650b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -340,7 +340,11 @@ class VirtualFunctionCallPreparationInterpreter( StringConstancyProperty.lb.stringConstancyInformation } else { val charSciValues = sciValues.filter(_.possibleStrings != "") map { sci ⇒ - sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + if (isIntegerValue(sci.possibleStrings)) { + sci.copy(possibleStrings = sci.possibleStrings.toInt.toChar.toString) + } else { + sci + } } StringConstancyInformation.reduceMultiple(charSciValues) } @@ -380,4 +384,9 @@ class VirtualFunctionCallPreparationInterpreter( ): EOptionP[Entity, StringConstancyProperty] = FinalEP(instr, InterpretationHandler.getStringConstancyPropertyForReplace) + /** + * Checks whether a given string is an integer value, i.e. contains only numbers. + */ + private def isIntegerValue(toTest: String): Boolean = toTest.forall(_.isDigit) + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala index 8243c1818c..ddddbe3be0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/preprocessing/PathTransformer.scala @@ -152,11 +152,9 @@ class PathTransformer(val interpretationHandler: InterpretationHandler) { StringTreeConst(StringConstancyProperty.lb.stringConstancyInformation) ) case _ ⇒ - val concatElement = StringTreeConcat( - path.elements.map { ne ⇒ - pathToTreeAcc(ne, fpe2Sci) - }.filter(_.isDefined).map(_.get).to[ListBuffer] - ) + val concatElement = StringTreeConcat(path.elements.map { ne ⇒ + pathToTreeAcc(ne, fpe2Sci) + }.filter(_.isDefined).map(_.get).to[ListBuffer]) // It might be that concat has only one child (because some interpreters might have // returned an empty list => In case of one child, return only that one if (concatElement.children.size == 1) { From 82b144448300284929431ffcaf02bc50071c2e87 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 21 Mar 2019 17:33:33 +0100 Subject: [PATCH 308/316] Added support for NewArray expressions. --- .../InterproceduralTestMethods.java | 18 +++ .../InterproceduralStringAnalysis.scala | 6 +- ...erpreter.scala => ArrayLoadPreparer.scala} | 6 +- ...InterproceduralInterpretationHandler.scala | 31 +++++- .../interprocedural/NewArrayPreparer.scala | 103 ++++++++++++++++++ ...nalizer.scala => ArrayLoadFinalizer.scala} | 14 ++- .../finalizer/NewArrayFinalizer.scala | 37 +++++++ 7 files changed, 200 insertions(+), 15 deletions(-) rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/{ArrayPreparationInterpreter.scala => ArrayLoadPreparer.scala} (97%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala rename OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/{ArrayFinalizer.scala => ArrayLoadFinalizer.scala} (79%) create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NewArrayFinalizer.scala diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index d3b5be4599..cb8ad9c37c 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -43,6 +43,8 @@ public class InterproceduralTestMethods { public static String someKey = "will not be revealed here"; + private String[] monthNames = { "January", "February", "March", getApril() }; + /** * {@see LocalTestMethods#analyzeString} */ @@ -666,6 +668,18 @@ public void getStaticFieldTest() { analyzeString(someKey); } + @StringDefinitionsCollection( + value = "a case where a String array field is read", + stringDefinitions = { + @StringDefinitions( + expectedLevel = CONSTANT, + expectedStrings = "(January|February|March|April)" + ) + }) + public void getStringArrayField(int i) { + analyzeString(monthNames[i]); + } + private String getRuntimeClassName() { return "java.lang.Runtime"; } @@ -698,4 +712,8 @@ private static String getHelperClass() { return "my.helper.Class"; } + private String getApril() { + return "April"; + } + } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index 3320765b43..af3dd7330e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -48,7 +48,7 @@ import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.NestedPathType import org.opalj.tac.ArrayLoad -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayLoadPreparer import org.opalj.tac.BinaryExpr import org.opalj.tac.Expr @@ -674,7 +674,7 @@ class InterproceduralStringAnalysis( private def hasParamUsageAlongPath(path: Path, stmts: Array[Stmt[V]]): Boolean = { def hasExprParamUsage(expr: Expr[V]): Boolean = expr match { case al: ArrayLoad[V] ⇒ - ArrayPreparationInterpreter.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) + ArrayLoadPreparer.getStoreAndLoadDefSites(al, stmts).exists(_ < 0) case duvar: V ⇒ duvar.definedBy.exists(_ < 0) case fc: FunctionCall[V] ⇒ fc.params.exists(hasExprParamUsage) case mc: MethodCall[V] ⇒ mc.params.exists(hasExprParamUsage) @@ -838,7 +838,7 @@ object InterproceduralStringAnalysis { */ def isSupportedType(typeName: String): Boolean = typeName == "char" || isSupportedPrimitiveNumberType(typeName) || - typeName == "java.lang.String" + typeName == "java.lang.String" || typeName == "java.lang.String[]" /** * Determines whether a given [[V]] element ([[DUVar]]) is supported by the string analysis. diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala similarity index 97% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala index 6b989603cc..2db756ba09 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/ArrayLoadPreparer.scala @@ -31,7 +31,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationSta * * @author Patrick Mell */ -class ArrayPreparationInterpreter( +class ArrayLoadPreparer( cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, state: InterproceduralComputationState, @@ -54,7 +54,7 @@ class ArrayPreparationInterpreter( val results = ListBuffer[EOptionP[Entity, StringConstancyProperty]]() val defSites = instr.arrayRef.asVar.definedBy.toArray - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( + val allDefSites = ArrayLoadPreparer.getStoreAndLoadDefSites( instr, state.tac.stmts ) @@ -103,7 +103,7 @@ class ArrayPreparationInterpreter( } -object ArrayPreparationInterpreter { +object ArrayLoadPreparer { type T = ArrayLoad[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 68fc747542..9f687a6b3d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -34,7 +34,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.BinaryE import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.DoubleValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.FloatValueInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.IntegerValueInterpreter -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.ArrayFinalizer +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.ArrayLoadFinalizer import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.InterpretationHandler import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.NewInterpreter import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.common.StringConstInterpreter @@ -49,6 +49,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedura import org.opalj.tac.SimpleValueConst import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.StaticFunctionCallFinalizer import org.opalj.tac.FieldRead +import org.opalj.tac.NewArray +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer.NewArrayFinalizer /** * `InterproceduralInterpretationHandler` is responsible for processing expressions that are @@ -106,6 +108,8 @@ class InterproceduralInterpretationHandler( case Assignment(_, _, expr: DoubleConst) ⇒ processConstExpr(expr, defSite) case Assignment(_, _, expr: ArrayLoad[V]) ⇒ processArrayLoad(expr, defSite, params) + case Assignment(_, _, expr: NewArray[V]) ⇒ + processNewArray(expr, defSite, params) case Assignment(_, _, expr: New) ⇒ processNew(expr, defSite) case Assignment(_, _, expr: GetStatic) ⇒ processGetField(expr, defSite) case ExprStmt(_, expr: GetStatic) ⇒ processGetField(expr, defSite) @@ -156,7 +160,26 @@ class InterproceduralInterpretationHandler( private def processArrayLoad( expr: ArrayLoad[V], defSite: Int, params: List[Seq[StringConstancyInformation]] ): EOptionP[Entity, StringConstancyProperty] = { - val r = new ArrayPreparationInterpreter( + val r = new ArrayLoadPreparer( + cfg, this, state, params + ).interpret(expr, defSite) + val sci = if (r.isFinal) { + r.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + } else { + processedDefSites.remove(defSite) + StringConstancyInformation.lb + } + state.appendToInterimFpe2Sci(defSite, sci) + r + } + + /** + * Helper / utility function for processing [[NewArray]]s. + */ + private def processNewArray( + expr: NewArray[V], defSite: Int, params: List[Seq[StringConstancyInformation]] + ): EOptionP[Entity, StringConstancyProperty] = { + val r = new NewArrayPreparer( cfg, this, state, params ).interpret(expr, defSite) val sci = if (r.isFinal) { @@ -365,7 +388,9 @@ class InterproceduralInterpretationHandler( case nvmc: NonVirtualMethodCall[V] ⇒ NonVirtualMethodCallFinalizer(state).finalizeInterpretation(nvmc, defSite) case Assignment(_, _, al: ArrayLoad[V]) ⇒ - ArrayFinalizer(state, cfg).finalizeInterpretation(al, defSite) + ArrayLoadFinalizer(state, cfg).finalizeInterpretation(al, defSite) + case Assignment(_, _, na: NewArray[V]) ⇒ + NewArrayFinalizer(state, cfg).finalizeInterpretation(na, defSite) case Assignment(_, _, vfc: VirtualFunctionCall[V]) ⇒ VirtualFunctionCallFinalizer(state, cfg).finalizeInterpretation(vfc, defSite) case ExprStmt(_, vfc: VirtualFunctionCall[V]) ⇒ diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala new file mode 100644 index 0000000000..14364a2ff5 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala @@ -0,0 +1,103 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.FinalEP +import org.opalj.br.cfg.CFG +import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.AbstractStringInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.ArrayStore +import org.opalj.tac.NewArray + +/** + * The `NewArrayPreparer` is responsible for preparing [[NewArray]] expressions. + *

    + * Not all (partial) results are guaranteed to be available at once, thus intermediate results + * might be produced. This interpreter will only compute the parts necessary to later on fully + * assemble the final result for the array interpretation. + * For more information, see the [[interpret]] method. + * + * @see [[AbstractStringInterpreter]] + * + * @author Patrick Mell + */ +class NewArrayPreparer( + cfg: CFG[Stmt[V], TACStmts[V]], + exprHandler: InterproceduralInterpretationHandler, + state: InterproceduralComputationState, + params: List[Seq[StringConstancyInformation]] +) extends AbstractStringInterpreter(cfg, exprHandler) { + + override type T = NewArray[V] + + /** + * @note This implementation will extend [[state.fpe2sci]] in a way that it adds the string + * constancy information for each definition site where it can compute a final result. All + * definition sites producing a refineable result will have to be handled later on to + * not miss this information. + * + * @note For this implementation, `defSite` plays a role! + * + * @see [[AbstractStringInterpreter.interpret]] + */ + override def interpret(instr: T, defSite: Int): EOptionP[Entity, StringConstancyProperty] = { + // Only support for 1-D arrays + if (instr.counts.length != 1) { + FinalEP(instr, StringConstancyProperty.lb) + } + + // Get all sites that define array values and process them + val arrValuesDefSites = + state.tac.stmts(defSite).asAssignment.targetVar.asVar.usedBy.toArray.toList.sorted + var allResults = arrValuesDefSites.filter { + ds ⇒ ds >= 0 && state.tac.stmts(ds).isInstanceOf[ArrayStore[V]] + }.flatMap { ds ⇒ + // ds holds a site an of array stores; these need to be evaluated for the actual values + state.tac.stmts(ds).asArrayStore.value.asVar.definedBy.toArray.toList.sorted.map { d ⇒ + val r = exprHandler.processDefSite(d, params) + if (r.isFinal) { + state.appendToFpe2Sci(d, r.asFinal.p.stringConstancyInformation) + } + r + } + } + + // Add information of parameters + arrValuesDefSites.filter(_ < 0).foreach { ds ⇒ + val paramPos = Math.abs(ds + 2) + // lb is the fallback value + val sci = StringConstancyInformation.reduceMultiple(params.map(_(paramPos))) + state.appendToFpe2Sci(ds, sci) + val e: Integer = ds + allResults ::= FinalEP(e, StringConstancyProperty(sci)) + } + + val interims = allResults.find(!_.isFinal) + if (interims.isDefined) { + interims.get + } else { + var resultSci = StringConstancyInformation.reduceMultiple(allResults.map { + _.asFinal.p.asInstanceOf[StringConstancyProperty].stringConstancyInformation + }) + // It might be that there are no results; in such a case, set the string information to + // the lower bound and manually add an entry to the results list + if (resultSci.isTheNeutralElement) { + resultSci = StringConstancyInformation.lb + } + if (allResults.isEmpty) { + val toAppend = FinalEP(instr, StringConstancyProperty(resultSci)) + allResults = toAppend :: allResults + } + state.appendToFpe2Sci(defSite, resultSci) + FinalEP(new Integer(defSite), StringConstancyProperty(resultSci)) + } + } + +} + diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala similarity index 79% rename from OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala rename to OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala index 19266cf7b4..71b3ae04f9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayFinalizer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/ArrayLoadFinalizer.scala @@ -9,13 +9,13 @@ import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.ArrayLoad import org.opalj.tac.Stmt import org.opalj.tac.TACStmts -import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayPreparationInterpreter +import org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.ArrayLoadPreparer import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState /** * @author Patrick Mell */ -class ArrayFinalizer( +class ArrayLoadFinalizer( state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] ) extends AbstractFinalizer(state) { @@ -27,7 +27,7 @@ class ArrayFinalizer( * @inheritdoc */ override def finalizeInterpretation(instr: T, defSite: Int): Unit = { - val allDefSites = ArrayPreparationInterpreter.getStoreAndLoadDefSites( + val allDefSites = ArrayLoadPreparer.getStoreAndLoadDefSites( instr, state.tac.stmts ) @@ -38,16 +38,18 @@ class ArrayFinalizer( } state.fpe2sci(defSite) = ListBuffer(StringConstancyInformation.reduceMultiple( - allDefSites.sorted.flatMap(state.fpe2sci(_)) + allDefSites.filter(state.fpe2sci.contains).sorted.flatMap { ds ⇒ + state.fpe2sci(ds) + } )) } } -object ArrayFinalizer { +object ArrayLoadFinalizer { def apply( state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] - ): ArrayFinalizer = new ArrayFinalizer(state, cfg) + ): ArrayLoadFinalizer = new ArrayLoadFinalizer(state, cfg) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NewArrayFinalizer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NewArrayFinalizer.scala new file mode 100644 index 0000000000..2e413c27e8 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/finalizer/NewArrayFinalizer.scala @@ -0,0 +1,37 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.string_analysis.interpretation.interprocedural.finalizer + +import org.opalj.br.cfg.CFG +import org.opalj.tac.fpcf.analyses.string_analysis.V +import org.opalj.tac.Stmt +import org.opalj.tac.TACStmts +import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationState +import org.opalj.tac.NewArray + +/** + * @author Patrick Mell + */ +class NewArrayFinalizer( + state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] +) extends AbstractFinalizer(state) { + + override type T = NewArray[V] + + /** + * Finalizes [[NewArray]]s. + *

    + * @inheritdoc + */ + override def finalizeInterpretation(instr: T, defSite: Int): Unit = + // Simply re-trigger the computation + state.iHandler.processDefSite(defSite) + +} + +object NewArrayFinalizer { + + def apply( + state: InterproceduralComputationState, cfg: CFG[Stmt[V], TACStmts[V]] + ): NewArrayFinalizer = new NewArrayFinalizer(state, cfg) + +} From 753a092c1d38a6d6ed61f43c2a08ad2f1174b875 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 25 Mar 2019 15:52:06 +0100 Subject: [PATCH 309/316] Approximate the results of a function, which does not return a String, with the lower bound. --- .../VirtualFunctionCallPreparationInterpreter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 87a195650b..7366d6415b 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -87,7 +87,7 @@ class VirtualFunctionCallPreparationInterpreter( interpretArbitraryCall(instr, defSite) case _ ⇒ val e: Integer = defSite - FinalEP(e, StringConstancyProperty.getNeutralElement) + FinalEP(e, StringConstancyProperty.lb) } } From 35f94c96a26ad0bbe9d30ac7d2887f5169e087bd Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 25 Mar 2019 16:07:33 +0100 Subject: [PATCH 310/316] Merge remote-tracking branch 'upstream/develop' into feature/string-analysis-new-develop --- .../br/fpcf/properties/EscapeProperty.scala | 108 +++++++- .../properties/ReturnValueFreshness.scala | 85 +++++- .../org/opalj/fpcf/par/EPKState.scala.temp | 260 ++++++++++++++++++ ...ore.scala => PKECPropertyStore.scala.temp} | 0 .../par/PKEFJPoolPropertyStore.scala.temp | 128 +++++++++ 5 files changed, 574 insertions(+), 7 deletions(-) create mode 100644 OPAL/si/src/main/scala/org/opalj/fpcf/par/EPKState.scala.temp rename OPAL/si/src/main/scala/org/opalj/fpcf/par/{PKECPropertyStore.scala => PKECPropertyStore.scala.temp} (100%) create mode 100644 OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala index 09eb301f2b..3610b2470f 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala @@ -11,6 +11,27 @@ import org.opalj.fpcf.ExplicitlyNamedProperty import org.opalj.fpcf.OrderedProperty import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.fpcf.PropertyStore +import org.opalj.br.analyses.VirtualFormalParameter +import org.opalj.br.instructions.ACONST_NULL +import org.opalj.br.instructions.ALOAD +import org.opalj.br.instructions.ALOAD_0 +import org.opalj.br.instructions.ALOAD_1 +import org.opalj.br.instructions.ALOAD_2 +import org.opalj.br.instructions.ALOAD_3 +import org.opalj.br.instructions.ARETURN +import org.opalj.br.instructions.ATHROW +import org.opalj.br.instructions.BIPUSH +import org.opalj.br.instructions.DLOAD +import org.opalj.br.instructions.FLOAD +import org.opalj.br.instructions.GETSTATIC +import org.opalj.br.instructions.ILOAD +import org.opalj.br.instructions.Instruction +import org.opalj.br.instructions.LDC +import org.opalj.br.instructions.LDC2_W +import org.opalj.br.instructions.LDC_W +import org.opalj.br.instructions.LLOAD +import org.opalj.br.instructions.SIPUSH sealed trait EscapePropertyMetaInformation extends PropertyMetaInformation { @@ -125,9 +146,9 @@ sealed trait EscapePropertyMetaInformation extends PropertyMetaInformation { * @author Florian Kuebler */ sealed abstract class EscapeProperty - extends OrderedProperty - with ExplicitlyNamedProperty - with EscapePropertyMetaInformation { + extends OrderedProperty + with ExplicitlyNamedProperty + with EscapePropertyMetaInformation { final def key: PropertyKey[EscapeProperty] = EscapeProperty.key @@ -187,7 +208,86 @@ object EscapeProperty extends EscapePropertyMetaInformation { final val Name = "opalj.EscapeProperty" - final lazy val key: PropertyKey[EscapeProperty] = PropertyKey.create(Name, AtMost(NoEscape)) + final lazy val key: PropertyKey[EscapeProperty] = PropertyKey.create( + Name, + AtMost(NoEscape), + fastTrack _ + ) + + private[this] def escapesViaReturnOrThrow(instruction: Instruction): Option[EscapeProperty] = { + instruction.opcode match { + case ARETURN.opcode ⇒ Some(EscapeViaReturn) + case ATHROW.opcode ⇒ Some(EscapeViaAbnormalReturn) + case _ ⇒ throw new IllegalArgumentException() + } + } + + def fastTrack(ps: PropertyStore, e: Entity): Option[EscapeProperty] = e match { + case fp @ VirtualFormalParameter(dm: DefinedMethod, _) if dm.definedMethod.body.isDefined ⇒ + val parameterIndex = fp.parameterIndex + if (parameterIndex >= 0 && dm.descriptor.parameterType(parameterIndex).isBaseType) + Some(NoEscape) + else { + val m = dm.definedMethod + val code = dm.definedMethod.body.get + if (code.codeSize == 1) { + Some(NoEscape) + } else if (code.codeSize == 2) { + if (m.descriptor.returnType.isBaseType) { + Some(NoEscape) + } else { + code.instructions(0).opcode match { + case ACONST_NULL.opcode ⇒ + Some(NoEscape) + + case ALOAD_0.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 0) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + + case ALOAD_1.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 1) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + case ALOAD_2.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 2) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + case ALOAD_3.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 3) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + } + } + } else if (code.codeSize == 3) { + code.instructions(0).opcode match { + case BIPUSH.opcode | ILOAD.opcode | FLOAD.opcode | + LLOAD.opcode | DLOAD.opcode | LDC.opcode ⇒ + Some(NoEscape) + case ALOAD.opcode ⇒ + val index = code.instructions(0).asInstanceOf[ALOAD].lvIndex + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, index) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(2)) + else + Some(NoEscape) + case _ ⇒ None + } + } else if (code.codeSize == 4) { + code.instructions(0).opcode match { + case LDC_W.opcode | LDC2_W.opcode | SIPUSH.opcode | GETSTATIC.opcode ⇒ + Some(NoEscape) + case _ ⇒ None + } + } else None + + } + case _ ⇒ + None + } } diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala index 39b06bdda4..ecf3632dc9 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala @@ -7,6 +7,20 @@ package properties import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.fpcf.PropertyStore +import org.opalj.br.instructions.AALOAD +import org.opalj.br.instructions.ACONST_NULL +import org.opalj.br.instructions.ALOAD +import org.opalj.br.instructions.ALOAD_0 +import org.opalj.br.instructions.ALOAD_1 +import org.opalj.br.instructions.ALOAD_2 +import org.opalj.br.instructions.ALOAD_3 +import org.opalj.br.instructions.ARETURN +import org.opalj.br.instructions.ATHROW +import org.opalj.br.instructions.Instruction +import org.opalj.br.instructions.LDC +import org.opalj.br.instructions.LDC_W +import org.opalj.br.instructions.NEWARRAY sealed trait ReturnValueFreshnessPropertyMetaInformation extends PropertyMetaInformation { final type Self = ReturnValueFreshness @@ -27,8 +41,8 @@ sealed trait ReturnValueFreshnessPropertyMetaInformation extends PropertyMetaInf * @author Florian Kuebler */ sealed abstract class ReturnValueFreshness - extends Property - with ReturnValueFreshnessPropertyMetaInformation { + extends Property + with ReturnValueFreshnessPropertyMetaInformation { final def key: PropertyKey[ReturnValueFreshness] = ReturnValueFreshness.key @@ -43,9 +57,74 @@ object ReturnValueFreshness extends ReturnValueFreshnessPropertyMetaInformation // Name of the property "ReturnValueFreshness", // fallback value - NoFreshReturnValue + NoFreshReturnValue, + fastTrackPropertyFunction _ ) + def fastTrackPropertyFunction( + ps: PropertyStore, dm: DeclaredMethod + ): Option[ReturnValueFreshness] = { + if (dm.descriptor.returnType.isBaseType) + Some(PrimitiveReturnValue) + else if (!dm.hasSingleDefinedMethod) + Some(NoFreshReturnValue) + else if (dm.declaringClassType.isArrayType && dm.descriptor == MethodDescriptor.JustReturnsObject && dm.name == "clone") + Some(FreshReturnValue) + else { + val m = dm.definedMethod + if (m.body.isEmpty) + Some(NoFreshReturnValue) + else { + val code = m.body.get + code.codeSize match { + case 2 ⇒ code.instructions(0).opcode match { + case ACONST_NULL.opcode ⇒ Some(FreshReturnValue) + case ALOAD_0.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) + case ALOAD_1.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) + case ALOAD_2.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) + case ALOAD_3.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) + case _ ⇒ None + } + case 3 ⇒ code.instructions(0).opcode match { + case LDC.opcode ⇒ Some(FreshReturnValue) + case ALOAD.opcode ⇒ normalAndAbnormalReturn(code.instructions(2)) + case _ ⇒ None + } + + case 4 ⇒ + val i1 = code.instructions(1) + val i2 = code.instructions(2) + if (i1 != null && i1.opcode == NEWARRAY.opcode) + Some(FreshReturnValue) + else if (code.instructions(0).opcode == LDC_W.opcode) + Some(FreshReturnValue) + else if (i2 != null && i2.opcode == AALOAD.opcode) + normalAndAbnormalReturn(code.instructions(3)) + else + None + + case 1 ⇒ throw new IllegalStateException(s"${m.toJava} unexpected bytecode: ${code.instructions.mkString}") + + case _ ⇒ None + } + } + } + } + + private[this] def normalAndAbnormalReturn( + instr: Instruction + ): Option[ReturnValueFreshness] = instr.opcode match { + case ATHROW.opcode ⇒ + println("asdasdansbjhwsbasfda") + Some(FreshReturnValue) + + case ARETURN.opcode ⇒ + Some(NoFreshReturnValue) + + case _ ⇒ + throw new IllegalArgumentException(s"unexpected instruction $instr") + } + } /** diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/EPKState.scala.temp b/OPAL/si/src/main/scala/org/opalj/fpcf/par/EPKState.scala.temp new file mode 100644 index 0000000000..e6e813e148 --- /dev/null +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/EPKState.scala.temp @@ -0,0 +1,260 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package par + +import java.util.concurrent.atomic.AtomicReference + +/** + * Encapsulates the state of a single entity and its property of a specific kind. + * + * @note All operations are effectively atomic operations. + */ +sealed trait EPKState { + + /** Returns the current property extension. */ + def eOptionP: SomeEOptionP + + /** Returns `true` if no property has been computed yet; `false` otherwise. */ + final def isEPK: Boolean = eOptionP.isEPK + + /** Returns `true` if this entity/property pair is not yet final. */ + def isRefinable: Boolean + + /** Returns the underlying entity. */ + final def e: Entity = eOptionP.e + + /** + * Updates the underlying `EOptionP` value. + * + * @note This function is only defined if the current `EOptionP` value is not already a + * final value. Hence, the client is required to handle (potentially) idempotent updates + * and to take care of appropriate synchronization. + */ + def update( + newEOptionP: SomeInterimEP, + c: OnUpdateContinuation, + dependees: Traversable[SomeEOptionP], + debug: Boolean + ): SomeEOptionP + + /** + * Adds the given E/PK as a depender on this E/PK instance. + * + * @note This operation is idempotent; that is, adding the same EPK multiple times has no + * special effect. + * @note Adding a depender to a FinalEPK is not supported. + */ + def addDepender(someEPK: SomeEPK): Unit + + /** + * Removes the given E/PK from the list of dependers of this EPKState. + * + * @note This method is always defined and never throws an exception for convenience purposes. + */ + def removeDepender(someEPK: SomeEPK): Unit + + def resetDependers(): Set[SomeEPK] + + def lastDependers(): Set[SomeEPK] + + /** + * Returns the current `OnUpdateComputation` or `null`, if the `OnUpdateComputation` was + * already triggered. This is an atomic operation. Additionally – in a second step – + * removes the EPK underlying the EPKState from the the dependees and clears the dependees. + * + * @note This method is always defined and never throws an exception. + */ + def clearOnUpdateComputationAndDependees(): OnUpdateContinuation + + /** + * Returns `true` if the current `EPKState` has an `OnUpdateComputation` that was not yet + * triggered. + * + * @note The returned value may have changed in the meantime; hence, this method + * can/should only be used as a hint. + */ + def hasPendingOnUpdateComputation: Boolean + + /** + * Returns `true` if and only if this EPKState has dependees. + * + * @note The set of dependees is only update when a property computation result is processed + * and there exists, w.r.t. an Entity/Property Kind pair, always at most one + * `PropertyComputationResult`. + */ + def hasDependees: Boolean + + /** + * Returns the current set of depeendes. Defined if and only if this `EPKState` is refinable. + * + * @note The set of dependees is only update when a property computation result is processed + * and there exists, w.r.t. an Entity/Property Kind pair, always at most one + * `PropertyComputationResult`. + */ + def dependees: Traversable[SomeEOptionP] + +} + +/** + * + * @param eOptionPAR An atomic reference holding the current property extension; we need to + * use an atomic reference to enable concurrent update operations as required + * by properties computed using partial results. + * The referenced `EOptionP` is never null. + * @param cAR The on update continuation function; null if triggered. + * @param dependees The dependees; never updated concurrently. + */ +final class InterimEPKState( + var eOptionP: SomeEOptionP, + val cAR: AtomicReference[OnUpdateContinuation], + @volatile var dependees: Traversable[SomeEOptionP], + var dependersAR: AtomicReference[Set[SomeEPK]] +) extends EPKState { + + assert(eOptionP.isRefinable) + + override def isRefinable: Boolean = true + + override def addDepender(someEPK: SomeEPK): Unit = { + val dependersAR = this.dependersAR + if (dependersAR == null) + return ; + + var prev, next: Set[SomeEPK] = null + do { + prev = dependersAR.get() + next = prev + someEPK + } while (!dependersAR.compareAndSet(prev, next)) + } + + override def removeDepender(someEPK: SomeEPK): Unit = { + val dependersAR = this.dependersAR + if (dependersAR == null) + return ; + + var prev, next: Set[SomeEPK] = null + do { + prev = dependersAR.get() + next = prev - someEPK + } while (!dependersAR.compareAndSet(prev, next)) + } + + override def lastDependers(): Set[SomeEPK] = { + val dependers = dependersAR.get() + dependersAR = null + dependers + } + + override def clearOnUpdateComputationAndDependees(): OnUpdateContinuation = { + val c = cAR.getAndSet(null) + dependees = Nil + c + } + + override def hasPendingOnUpdateComputation: Boolean = cAR.get() != null + + override def update( + eOptionP: SomeInterimEP, + c: OnUpdateContinuation, + dependees: Traversable[SomeEOptionP], + debug: Boolean + ): SomeEOptionP = { + val oldEOptionP = this.eOptionP + if (debug) oldEOptionP.checkIsValidPropertiesUpdate(eOptionP, dependees) + + this.eOptionP = eOptionP + + val oldOnUpdateContinuation = cAR.getAndSet(c) + assert(oldOnUpdateContinuation == null) + + assert(this.dependees.isEmpty) + this.dependees = dependees + + oldEOptionP + } + + override def hasDependees: Boolean = dependees.nonEmpty + + override def toString: String = { + "InterimEPKState("+ + s"eOptionP=${eOptionPAR.get},"+ + s","+ + s"dependees=$dependees,"+ + s"dependers=${dependersAR.get()})" + } +} + +final class FinalEPKState(override val eOptionP: SomeEOptionP) extends EPKState { + + override def isRefinable: Boolean = false + + override def update(newEOptionP: SomeInterimEP, debug: Boolean): SomeEOptionP = { + throw new UnknownError(s"the final property $eOptionP can't be updated to $newEOptionP") + } + + override def resetDependers(): Set[SomeEPK] = { + throw new UnknownError(s"the final property $eOptionP can't have dependers") + } + + override def lastDependers(): Set[SomeEPK] = { + throw new UnknownError(s"the final property $eOptionP can't have dependers") + } + + override def addDepender(epk: SomeEPK): Unit = { + throw new UnknownError(s"final properties can't have dependers") + } + + override def removeDepender(someEPK: SomeEPK): Unit = { /* There is nothing to do! */ } + + override def clearOnUpdateComputationAndDependees(): OnUpdateContinuation = { + null + } + + override def dependees: Traversable[SomeEOptionP] = { + throw new UnknownError("final properties don't have dependees") + } + + override def hasDependees: Boolean = false + + override def setOnUpdateComputationAndDependees( + c: OnUpdateContinuation, + dependees: Traversable[SomeEOptionP] + ): Unit = { + throw new UnknownError("final properties can't have \"OnUpdateContinuations\"") + } + + override def hasPendingOnUpdateComputation: Boolean = false + + override def toString: String = s"FinalEPKState(finalEP=$eOptionP)" +} + +object EPKState { + + def apply(finalEP: SomeFinalEP): EPKState = new FinalEPKState(finalEP) + + def apply(eOptionP: SomeEOptionP): EPKState = { + new InterimEPKState( + new AtomicReference[SomeEOptionP](eOptionP), + new AtomicReference[OnUpdateContinuation]( /*null*/ ), + Nil, + new AtomicReference[Set[SomeEPK]](Set.empty) + ) + } + + def apply( + eOptionP: SomeEOptionP, + c: OnUpdateContinuation, + dependees: Traversable[SomeEOptionP] + ): EPKState = { + new InterimEPKState( + new AtomicReference[SomeEOptionP](eOptionP), + new AtomicReference[OnUpdateContinuation](c), + dependees, + new AtomicReference[Set[SomeEPK]](Set.empty) + ) + } + + def unapply(epkState: EPKState): Some[SomeEOptionP] = Some(epkState.eOptionP) + +} diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala.temp similarity index 100% rename from OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala rename to OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala.temp diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp new file mode 100644 index 0000000000..a01fb559a4 --- /dev/null +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp @@ -0,0 +1,128 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package par + +import java.util.concurrent.ForkJoinPool + +import java.util.concurrent.TimeUnit + +import org.opalj.log.LogContext + +/** + * A concurrent implementation of the property store which executes the scheduled computations + * in parallel using a ForkJoinPool. + * + * We use `NumberOfThreadsForProcessingPropertyComputations` threads for processing the + * scheduled computations. + * + * @author Michael Eichberg + */ +final class PKEFJPoolPropertyStore private ( + val ctx: Map[Class[_], AnyRef], + val NumberOfThreadsForProcessingPropertyComputations: Int +)( + implicit + val logContext: LogContext +) extends PKECPropertyStore { store ⇒ + + private[this] val pool: ForkJoinPool = { + new ForkJoinPool( + 128 /*NumberOfThreadsForProcessingPropertyComputations*/ , + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + (_: Thread, e: Throwable) ⇒ { collectException(e) }, + false + ) + } + + override protected[this] def awaitPoolQuiescence(): Unit = handleExceptions { + if (!pool.awaitQuiescence(Long.MaxValue, TimeUnit.DAYS)) { + throw new UnknownError("pool failed to reach quiescence") + } + // exceptions that are thrown in a pool's thread are "only" collected and, hence, + // may not "immediately" trigger the termination. + if (exception != null) throw exception; + } + + override def shutdown(): Unit = pool.shutdown() + + override def isIdle: Boolean = pool.isQuiescent + + override protected[this] def parallelize(r: Runnable): Unit = pool.execute(r) + + override protected[this] def forkPropertyComputation[E <: Entity]( + e: E, + pc: PropertyComputation[E] + ): Unit = { + pool.execute(() ⇒ { + if (doTerminate) throw new InterruptedException(); + + store.processResult(pc(e)) + }) + incrementScheduledTasksCounter() + } + + override protected[this] def forkResultHandler(r: PropertyComputationResult): Unit = { + pool.execute(() ⇒ { + if (doTerminate) throw new InterruptedException(); + + store.processResult(r) + }) + incrementScheduledTasksCounter() + } + + override protected[this] def forkOnUpdateContinuation( + c: OnUpdateContinuation, + e: Entity, + pk: SomePropertyKey + ): Unit = { + pool.execute(() ⇒ { + if (doTerminate) throw new InterruptedException(); + + // get the newest value before we actually call the onUpdateContinuation + val newEPS = store(e, pk).asEPS + // IMPROVE ... see other forkOnUpdateContinuation + store.processResult(c(newEPS)) + }) + incrementOnUpdateContinuationsCounter() + } + + override protected[this] def forkOnUpdateContinuation( + c: OnUpdateContinuation, + finalEP: SomeFinalEP + ): Unit = { + pool.execute(() ⇒ { + if (doTerminate) throw new InterruptedException(); + + // IMPROVE: Instead of naively calling "c" with finalEP, it may be worth considering which other updates have happened to figure out which update may be the "best" + store.processResult(c(finalEP)) + }) + incrementOnUpdateContinuationsCounter() + } + +} + +object PKEFJPoolPropertyStore extends ParallelPropertyStoreFactory { + + def apply( + context: PropertyStoreContext[_ <: AnyRef]* + )( + implicit + logContext: LogContext + ): PKECPropertyStore = { + val contextMap: Map[Class[_], AnyRef] = context.map(_.asTuple).toMap + new PKEFJPoolPropertyStore(contextMap, NumberOfThreadsForProcessingPropertyComputations) + } + + def create( + context: Map[Class[_], AnyRef] // ,PropertyStoreContext[_ <: AnyRef]* + )( + implicit + logContext: LogContext + ): PKECPropertyStore = { + + new PKEFJPoolPropertyStore(context, NumberOfThreadsForProcessingPropertyComputations) + } + +} + From 81e11c9f5e2a057752cbba770565dab07ce1b4ec Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 2 Apr 2019 20:24:41 +0200 Subject: [PATCH 311/316] Changed the way the (interprocedural) string analysis tests are executed. --- .../org/opalj/fpcf/StringAnalysisTest.scala | 164 ++++++++++-------- 1 file changed, 91 insertions(+), 73 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index f099fd6b3a..d713ffadba 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -8,9 +8,8 @@ import java.net.URL import scala.collection.mutable import scala.collection.mutable.ListBuffer -import org.opalj.log.LogContext +import org.opalj.collection.immutable.Chain import org.opalj.collection.immutable.ConstArray -import org.opalj.fpcf.seq.PKESequentialPropertyStore import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method @@ -25,6 +24,7 @@ import org.opalj.br.fpcf.cg.properties.ThreadRelatedIncompleteCallSites import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis +import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall @@ -33,6 +33,7 @@ import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis +import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis @@ -42,8 +43,6 @@ import org.opalj.tac.fpcf.analyses.TACAITransformer import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis -import org.opalj.tac.DefaultTACAIKey -import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -57,9 +56,6 @@ sealed class StringAnalysisTestRunner( val filesToLoad: List[String] ) extends PropertiesTest { - private val fqStringDefAnnotation = - "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" - /** * @return Returns all relevant project files (NOT including library files) to run the tests. */ @@ -73,33 +69,6 @@ sealed class StringAnalysisTestRunner( necessaryFiles.map { filePath ⇒ new File(basePath + filePath) } } - /** - * Extracts [[org.opalj.tac.UVar]]s from a set of statements. The locations of the UVars are - * identified by the argument to the very first call to LocalTestMethods#analyzeString. - * - * @param cfg The control flow graph from which to extract the UVar, usually derived from the - * method that contains the call(s) to LocalTestMethods#analyzeString. - * @return Returns the arguments of the LocalTestMethods#analyzeString as a DUVars list in the - * order in which they occurred in the given statements. - */ - def extractUVars(cfg: CFG[Stmt[V], TACStmts[V]]): List[V] = { - cfg.code.instructions.filter { - case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ - declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod - case _ ⇒ false - }.map(_.asVirtualMethodCall.params.head.asVar).toList - } - - /** - * Takes an annotation and checks if it is a - * [[org.opalj.fpcf.properties.string_analysis.StringDefinitions]] annotation. - * - * @param a The annotation to check. - * @return True if the `a` is of type StringDefinitions and false otherwise. - */ - def isStringUsageAnnotation(a: Annotation): Boolean = - a.annotationType.toJavaClass.getName == fqStringDefAnnotation - /** * Extracts a `StringDefinitions` annotation from a `StringDefinitionsCollection` annotation. * Make sure that you pass an instance of `StringDefinitionsCollection` and that the element at @@ -114,9 +83,9 @@ sealed class StringAnalysisTestRunner( a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation def determineEAS( - p: Project[URL], - ps: PropertyStore, - allMethodsWithBody: ConstArray[Method], + p: Project[URL], + ps: PropertyStore, + allMethodsWithBody: ConstArray[Method] ): Traversable[((V, Method), String ⇒ String, List[Annotation])] = { // We need a "method to entity" matching for the evaluation (see further below) val m2e = mutable.HashMap[Method, Entity]() @@ -124,17 +93,19 @@ sealed class StringAnalysisTestRunner( val tacProvider = p.get(DefaultTACAIKey) allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( - (exists, a) ⇒ exists || isStringUsageAnnotation(a) + (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) ) } foreach { m ⇒ - extractUVars(tacProvider(m).cfg).foreach { uvar ⇒ - if (!m2e.contains(m)) { - m2e += m → ListBuffer(uvar) - } else { - m2e(m).asInstanceOf[ListBuffer[V]].append(uvar) + StringAnalysisTestRunner.extractUVars( + tacProvider(m).cfg, fqTestMethodsClass, nameTestMethod + ).foreach { uvar ⇒ + if (!m2e.contains(m)) { + m2e += m → ListBuffer(uvar) + } else { + m2e(m).asInstanceOf[ListBuffer[V]].append(uvar) + } + //ps.force((uvar, m), StringConstancyProperty.key) } - ps.force((uvar, m), StringConstancyProperty.key) - } } // As entity, we need not the method but a tuple (DUVar, Method), thus this transformation @@ -154,6 +125,44 @@ sealed class StringAnalysisTestRunner( } +object StringAnalysisTestRunner { + + private val fqStringDefAnnotation = + "org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection" + + /** + * Takes an annotation and checks if it is a + * [[org.opalj.fpcf.properties.string_analysis.StringDefinitions]] annotation. + * + * @param a The annotation to check. + * @return True if the `a` is of type StringDefinitions and false otherwise. + */ + def isStringUsageAnnotation(a: Annotation): Boolean = + a.annotationType.toJavaClass.getName == fqStringDefAnnotation + + /** + * Extracts [[org.opalj.tac.UVar]]s from a set of statements. The locations of the UVars are + * identified by the argument to the very first call to LocalTestMethods#analyzeString. + * + * @param cfg The control flow graph from which to extract the UVar, usually derived from the + * method that contains the call(s) to LocalTestMethods#analyzeString. + * @return Returns the arguments of the LocalTestMethods#analyzeString as a DUVars list in the + * order in which they occurred in the given statements. + */ + def extractUVars( + cfg: CFG[Stmt[V], TACStmts[V]], + fqTestMethodsClass: String, + nameTestMethod: String + ): List[V] = { + cfg.code.instructions.filter { + case VirtualMethodCall(_, declClass, _, name, _, _, _) ⇒ + declClass.toJavaClass.getName == fqTestMethodsClass && name == nameTestMethod + case _ ⇒ false + }.map(_.asVirtualMethodCall.params.head.asVar).toList + } + +} + /** * Tests whether the [[IntraproceduralStringAnalysis]] works correctly with respect to some * well-defined tests. @@ -202,15 +211,31 @@ object IntraproceduralStringAnalysisTest { * Tests whether the InterproceduralStringAnalysis works correctly with respect to some * well-defined tests. * - * @note We could use a manager to run the analyses, however, doing so leads to the fact that the - * property store does not compute all properties, especially TAC. The reason being is that - * a ''shutdown'' call prevents further computations. Thus, we do all this manually here. - * (Detected and fixed in a session with Dominik.) - * * @author Patrick Mell */ class InterproceduralStringAnalysisTest extends PropertiesTest { + private def determineEntitiesToAnalyze( + project: Project[URL] + ): Iterable[(V, Method)] = { + val entitiesToAnalyze = ListBuffer[(V, Method)]() + val tacProvider = project.get(DefaultTACAIKey) + project.allMethodsWithBody.filter { + _.runtimeInvisibleAnnotations.foldLeft(false)( + (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) + ) + } foreach { m ⇒ + StringAnalysisTestRunner.extractUVars( + tacProvider(m).cfg, + InterproceduralStringAnalysisTest.fqTestMethodsClass, + InterproceduralStringAnalysisTest.nameTestMethod + ).foreach { uvar ⇒ + entitiesToAnalyze.append((uvar, m)) + } + } + entitiesToAnalyze + } + describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { val runner = new StringAnalysisTestRunner( InterproceduralStringAnalysisTest.fqTestMethodsClass, @@ -218,15 +243,10 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { InterproceduralStringAnalysisTest.filesToLoad ) val p = Project(runner.getRelevantProjectFiles, Array[File]()) - p.getOrCreateProjectInformationKeyInitializationData( - PropertyStoreKey, - (context:List[PropertyStoreContext[AnyRef]]) ⇒ { - implicit val lg:LogContext = p.logContext - PropertyStore.updateTraceFallbacks(true) - PKESequentialPropertyStore.apply(context:_*) - }) - - val analyses: Set[ComputationSpecification[FPCFAnalysis]] = Set(TACAITransformer, + + val manager = p.get(FPCFAnalysesManagerKey) + val analysesToRun = Set( + TACAITransformer, LazyL0BaseAIAnalysis, RTACallGraphAnalysisScheduler, TriggeredStaticInitializerAnalysis, @@ -245,24 +265,22 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { )), LazyInterproceduralStringAnalysis ) - val ps = p.get(PropertyStoreKey) - ps.setupPhase(analyses.flatMap(_.derives.map(_.pk))) - var initData: Map[ComputationSpecification[_], Any] = Map() - analyses.foreach{a ⇒ - initData += a → a.init(ps) - a.beforeSchedule(ps) - } - var scheduledAnalyses: List[FPCFAnalysis] = List() - analyses.foreach{a ⇒ - scheduledAnalyses ::=a.schedule(ps, initData(a).asInstanceOf[a.InitializationData]) - } - val testContext = TestContext(p, ps, scheduledAnalyses) + val ps = p.get(PropertyStoreKey) + val (_, analyses) = manager.runAll( + analysesToRun, + { _: Chain[ComputationSpecification[FPCFAnalysis]] ⇒ + determineEntitiesToAnalyze(p).foreach(ps.force(_, StringConstancyProperty.key)) + } + ) + val testContext = TestContext(p, ps, analyses.map(_._2)) val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) - validateProperties(testContext, eas, Set("StringConstancy")) - testContext.propertyStore.shutdown() + ps.waitOnPhaseCompletion() + ps.shutdown() + + validateProperties(testContext, eas, Set("StringConstancy")) } } From 4587e602b38944ee45ba6acda46048543d954167 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 2 Apr 2019 20:25:30 +0200 Subject: [PATCH 312/316] Merge remote-tracking branch 'upstream/develop' into feature/string-analysis-new-develop --- .../br/fpcf/properties/EscapeProperty.scala | 102 +++++++++--------- .../properties/ReturnValueFreshness.scala | 9 +- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala index 3610b2470f..d6af8c8fae 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala @@ -146,9 +146,9 @@ sealed trait EscapePropertyMetaInformation extends PropertyMetaInformation { * @author Florian Kuebler */ sealed abstract class EscapeProperty - extends OrderedProperty - with ExplicitlyNamedProperty - with EscapePropertyMetaInformation { + extends OrderedProperty + with ExplicitlyNamedProperty + with EscapePropertyMetaInformation { final def key: PropertyKey[EscapeProperty] = EscapeProperty.key @@ -230,59 +230,63 @@ object EscapeProperty extends EscapePropertyMetaInformation { else { val m = dm.definedMethod val code = dm.definedMethod.body.get - if (code.codeSize == 1) { - Some(NoEscape) - } else if (code.codeSize == 2) { - if (m.descriptor.returnType.isBaseType) { + code.codeSize match { + case 1 ⇒ Some(NoEscape) - } else { - code.instructions(0).opcode match { - case ACONST_NULL.opcode ⇒ - Some(NoEscape) - - case ALOAD_0.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 0) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else + case 2 ⇒ + if (m.descriptor.returnType.isBaseType) { + Some(NoEscape) + } else { + code.instructions(0).opcode match { + case ACONST_NULL.opcode ⇒ Some(NoEscape) - case ALOAD_1.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 1) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else - Some(NoEscape) - case ALOAD_2.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 2) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else - Some(NoEscape) - case ALOAD_3.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 3) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) + case ALOAD_0.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 0) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + + case ALOAD_1.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 1) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + case ALOAD_2.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 2) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + case ALOAD_3.opcode ⇒ + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 3) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(1)) + else + Some(NoEscape) + } + } + case 3 ⇒ + code.instructions(0).opcode match { + case BIPUSH.opcode | ILOAD.opcode | FLOAD.opcode | + LLOAD.opcode | DLOAD.opcode | LDC.opcode ⇒ + Some(NoEscape) + case ALOAD.opcode ⇒ + val index = code.instructions(0).asInstanceOf[ALOAD].lvIndex + if (registerIndexToParameterIndex(m.isStatic, m.descriptor, index) == parameterIndex) + escapesViaReturnOrThrow(code.instructions(2)) else Some(NoEscape) + case _ ⇒ None } - } - } else if (code.codeSize == 3) { - code.instructions(0).opcode match { - case BIPUSH.opcode | ILOAD.opcode | FLOAD.opcode | - LLOAD.opcode | DLOAD.opcode | LDC.opcode ⇒ - Some(NoEscape) - case ALOAD.opcode ⇒ - val index = code.instructions(0).asInstanceOf[ALOAD].lvIndex - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, index) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(2)) - else + case 4 ⇒ + code.instructions(0).opcode match { + case LDC_W.opcode | LDC2_W.opcode | SIPUSH.opcode | GETSTATIC.opcode ⇒ Some(NoEscape) - case _ ⇒ None - } - } else if (code.codeSize == 4) { - code.instructions(0).opcode match { - case LDC_W.opcode | LDC2_W.opcode | SIPUSH.opcode | GETSTATIC.opcode ⇒ - Some(NoEscape) - case _ ⇒ None - } - } else None + case _ ⇒ None + } + + case _ ⇒ + None + } } case _ ⇒ diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala index ecf3632dc9..a5fb9b9e57 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala @@ -41,8 +41,8 @@ sealed trait ReturnValueFreshnessPropertyMetaInformation extends PropertyMetaInf * @author Florian Kuebler */ sealed abstract class ReturnValueFreshness - extends Property - with ReturnValueFreshnessPropertyMetaInformation { + extends Property + with ReturnValueFreshnessPropertyMetaInformation { final def key: PropertyKey[ReturnValueFreshness] = ReturnValueFreshness.key @@ -114,14 +114,13 @@ object ReturnValueFreshness extends ReturnValueFreshnessPropertyMetaInformation private[this] def normalAndAbnormalReturn( instr: Instruction ): Option[ReturnValueFreshness] = instr.opcode match { - case ATHROW.opcode ⇒ - println("asdasdansbjhwsbasfda") + case ATHROW.opcode ⇒ Some(FreshReturnValue) case ARETURN.opcode ⇒ Some(NoFreshReturnValue) - case _ ⇒ + case _ ⇒ throw new IllegalArgumentException(s"unexpected instruction $instr") } From d4a719501dc250682b4d18a7d03b938d7cb67ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20K=C3=BCbler?= Date: Tue, 7 Jan 2020 13:14:36 +0100 Subject: [PATCH 313/316] re-applied review comments from PR #1 --- .../org/opalj/support/info/ClassUsageAnalysis.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala index ca15ff7614..5d95d774f1 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala @@ -22,13 +22,12 @@ import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.cg.V /** - * Analyzes a project for how a particular class is used within that project. This means that this - * analysis collect information on which methods are called on objects of that class as well as how - * often. + * Analyzes a project for how a particular class is used within that project. Collects information + * on which methods are called on objects of that class as well as how often. * - * The analysis can be configured by passing the following optional parameters: `class` (the class - * to analyze), `granularity` (fine- or coarse-grained; defines which information will be gathered - * by an analysis run). For further information see + * The analysis can be configured by passing the following parameters: `class` (the class to + * analyze) and `granularity` (fine or coarse; defines which information will be gathered by an + * analysis run; this parameter is optional). For further information see * [[ClassUsageAnalysis.analysisSpecificParametersDescription]]. * * @author Patrick Mell From f00721b813d30910c1aebc1abe468efb706e015d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20K=C3=BCbler?= Date: Tue, 7 Jan 2020 13:39:55 +0100 Subject: [PATCH 314/316] sync with develop --- ...ore.scala.temp => PKECPropertyStore.scala} | 0 .../par/PKEFJPoolPropertyStore.scala.temp | 128 ------------------ 2 files changed, 128 deletions(-) rename OPAL/si/src/main/scala/org/opalj/fpcf/par/{PKECPropertyStore.scala.temp => PKECPropertyStore.scala} (100%) delete mode 100644 OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala.temp b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala similarity index 100% rename from OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala.temp rename to OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp deleted file mode 100644 index a01fb559a4..0000000000 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKEFJPoolPropertyStore.scala.temp +++ /dev/null @@ -1,128 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package fpcf -package par - -import java.util.concurrent.ForkJoinPool - -import java.util.concurrent.TimeUnit - -import org.opalj.log.LogContext - -/** - * A concurrent implementation of the property store which executes the scheduled computations - * in parallel using a ForkJoinPool. - * - * We use `NumberOfThreadsForProcessingPropertyComputations` threads for processing the - * scheduled computations. - * - * @author Michael Eichberg - */ -final class PKEFJPoolPropertyStore private ( - val ctx: Map[Class[_], AnyRef], - val NumberOfThreadsForProcessingPropertyComputations: Int -)( - implicit - val logContext: LogContext -) extends PKECPropertyStore { store ⇒ - - private[this] val pool: ForkJoinPool = { - new ForkJoinPool( - 128 /*NumberOfThreadsForProcessingPropertyComputations*/ , - ForkJoinPool.defaultForkJoinWorkerThreadFactory, - (_: Thread, e: Throwable) ⇒ { collectException(e) }, - false - ) - } - - override protected[this] def awaitPoolQuiescence(): Unit = handleExceptions { - if (!pool.awaitQuiescence(Long.MaxValue, TimeUnit.DAYS)) { - throw new UnknownError("pool failed to reach quiescence") - } - // exceptions that are thrown in a pool's thread are "only" collected and, hence, - // may not "immediately" trigger the termination. - if (exception != null) throw exception; - } - - override def shutdown(): Unit = pool.shutdown() - - override def isIdle: Boolean = pool.isQuiescent - - override protected[this] def parallelize(r: Runnable): Unit = pool.execute(r) - - override protected[this] def forkPropertyComputation[E <: Entity]( - e: E, - pc: PropertyComputation[E] - ): Unit = { - pool.execute(() ⇒ { - if (doTerminate) throw new InterruptedException(); - - store.processResult(pc(e)) - }) - incrementScheduledTasksCounter() - } - - override protected[this] def forkResultHandler(r: PropertyComputationResult): Unit = { - pool.execute(() ⇒ { - if (doTerminate) throw new InterruptedException(); - - store.processResult(r) - }) - incrementScheduledTasksCounter() - } - - override protected[this] def forkOnUpdateContinuation( - c: OnUpdateContinuation, - e: Entity, - pk: SomePropertyKey - ): Unit = { - pool.execute(() ⇒ { - if (doTerminate) throw new InterruptedException(); - - // get the newest value before we actually call the onUpdateContinuation - val newEPS = store(e, pk).asEPS - // IMPROVE ... see other forkOnUpdateContinuation - store.processResult(c(newEPS)) - }) - incrementOnUpdateContinuationsCounter() - } - - override protected[this] def forkOnUpdateContinuation( - c: OnUpdateContinuation, - finalEP: SomeFinalEP - ): Unit = { - pool.execute(() ⇒ { - if (doTerminate) throw new InterruptedException(); - - // IMPROVE: Instead of naively calling "c" with finalEP, it may be worth considering which other updates have happened to figure out which update may be the "best" - store.processResult(c(finalEP)) - }) - incrementOnUpdateContinuationsCounter() - } - -} - -object PKEFJPoolPropertyStore extends ParallelPropertyStoreFactory { - - def apply( - context: PropertyStoreContext[_ <: AnyRef]* - )( - implicit - logContext: LogContext - ): PKECPropertyStore = { - val contextMap: Map[Class[_], AnyRef] = context.map(_.asTuple).toMap - new PKEFJPoolPropertyStore(contextMap, NumberOfThreadsForProcessingPropertyComputations) - } - - def create( - context: Map[Class[_], AnyRef] // ,PropertyStoreContext[_ <: AnyRef]* - )( - implicit - logContext: LogContext - ): PKECPropertyStore = { - - new PKEFJPoolPropertyStore(context, NumberOfThreadsForProcessingPropertyComputations) - } - -} - From f9ae6318e1478cb1f4240526d399690b99eaa352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20K=C3=BCbler?= Date: Tue, 7 Jan 2020 15:25:55 +0100 Subject: [PATCH 315/316] resolved merge conflicts --- .../support/info/ClassUsageAnalysis.scala | 8 +- .../info/StringAnalysisReflectiveCalls.scala | 39 +------ .../InterproceduralTestMethods.java | 20 ---- .../org/opalj/fpcf/StringAnalysisTest.scala | 41 +------ .../src/main/scala/org/opalj/br/cfg/CFG.scala | 15 --- .../br/fpcf/properties/EscapeProperty.scala | 104 +----------------- .../properties/ReturnValueFreshness.scala | 81 +------------- .../properties/StringConstancyProperty.scala | 2 +- .../InterproceduralComputationState.scala | 6 +- .../InterproceduralStringAnalysis.scala | 20 ++-- .../IntraproceduralStringAnalysis.scala | 6 +- .../AbstractStringInterpreter.scala | 2 +- .../InterproceduralFieldInterpreter.scala | 6 +- ...InterproceduralInterpretationHandler.scala | 6 +- ...ralNonVirtualFunctionCallInterpreter.scala | 2 +- ...duralNonVirtualMethodCallInterpreter.scala | 4 +- ...ceduralStaticFunctionCallInterpreter.scala | 2 +- ...oceduralVirtualMethodCallInterpreter.scala | 2 +- .../interprocedural/NewArrayPreparer.scala | 2 +- ...alFunctionCallPreparationInterpreter.scala | 2 +- .../test/scala/org/opalj/tac/TACAITest.scala | 102 ++++++++--------- 21 files changed, 97 insertions(+), 375 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala index 5d95d774f1..805be36f21 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/ClassUsageAnalysis.scala @@ -11,15 +11,15 @@ import scala.collection.mutable.ListBuffer import org.opalj.log.GlobalLogContext import org.opalj.log.OPALLogger import org.opalj.br.analyses.BasicReport -import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project +import org.opalj.br.analyses.ProjectAnalysisApplication import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt -import org.opalj.tac.SimpleTACAIKey import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.cg.V +import org.opalj.tac.EagerDetachedTACAIKey /** * Analyzes a project for how a particular class is used within that project. Collects information @@ -32,7 +32,7 @@ import org.opalj.tac.fpcf.analyses.cg.V * * @author Patrick Mell */ -object ClassUsageAnalysis extends DefaultOneStepAnalysis { +object ClassUsageAnalysis extends ProjectAnalysisApplication { implicit val logContext: GlobalLogContext.type = GlobalLogContext @@ -139,7 +139,7 @@ object ClassUsageAnalysis extends DefaultOneStepAnalysis { ): ReportableAnalysisResult = { setAnalysisParameters(parameters) val resultMap = mutable.Map[String, Int]() - val tacProvider = project.get(SimpleTACAIKey) + val tacProvider = project.get(EagerDetachedTACAIKey) project.allMethodsWithBody.foreach { m ⇒ tacProvider(m).stmts.foreach { stmt ⇒ diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala index bb1ea53917..4dfc5cb557 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/StringAnalysisReflectiveCalls.scala @@ -18,7 +18,6 @@ import org.opalj.fpcf.Result import org.opalj.fpcf.SomeEPS import org.opalj.value.ValueInformation import org.opalj.br.analyses.BasicReport -import org.opalj.br.analyses.DefaultOneStepAnalysis import org.opalj.br.analyses.Project import org.opalj.br.analyses.ReportableAnalysisResult import org.opalj.br.instructions.Instruction @@ -26,15 +25,11 @@ import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.ReferenceType import org.opalj.br.instructions.INVOKEVIRTUAL import org.opalj.br.Method +import org.opalj.br.analyses.ProjectAnalysisApplication import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.FPCFAnalysesManagerKey -import org.opalj.br.fpcf.cg.properties.ReflectionRelatedCallees -import org.opalj.br.fpcf.cg.properties.SerializationRelatedCallees -import org.opalj.br.fpcf.cg.properties.StandardInvokeCallees -import org.opalj.br.fpcf.cg.properties.ThreadRelatedIncompleteCallSites -import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis import org.opalj.tac.Assignment import org.opalj.tac.Call import org.opalj.tac.ExprStmt @@ -42,22 +37,12 @@ import org.opalj.tac.StaticFunctionCall import org.opalj.tac.VirtualFunctionCall import org.opalj.tac.fpcf.analyses.string_analysis.P import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.fpcf.analyses.TACAITransformer -import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.DUVar import org.opalj.tac.Stmt import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode -import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler -import org.opalj.tac.fpcf.analyses.cg.TriggeredStaticInitializerAnalysis -import org.opalj.tac.fpcf.analyses.cg.reflection.TriggeredReflectionRelatedCallsAnalysis -import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler -import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredThreadRelatedCallsAnalysis +import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis /** @@ -76,7 +61,7 @@ import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnal * * @author Patrick Mell */ -object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { +object StringAnalysisReflectiveCalls extends ProjectAnalysisApplication { private type ResultMapType = mutable.Map[String, ListBuffer[StringConstancyInformation]] @@ -268,24 +253,8 @@ object StringAnalysisReflectiveCalls extends DefaultOneStepAnalysis { project: Project[URL], parameters: Seq[String], isInterrupted: () ⇒ Boolean ): ReportableAnalysisResult = { val manager = project.get(FPCFAnalysesManagerKey) + project.get(RTACallGraphKey) implicit val (propertyStore, analyses) = manager.runAll( - TACAITransformer, - LazyL0BaseAIAnalysis, - RTACallGraphAnalysisScheduler, - TriggeredStaticInitializerAnalysis, - TriggeredLoadedClassesAnalysis, - TriggeredFinalizerAnalysisScheduler, - TriggeredThreadRelatedCallsAnalysis, - TriggeredSerializationRelatedCallsAnalysis, - TriggeredReflectionRelatedCallsAnalysis, - TriggeredSystemPropertiesAnalysis, - TriggeredInstantiatedTypesAnalysis, - LazyCalleesAnalysis(Set( - StandardInvokeCallees, - SerializationRelatedCallees, - ReflectionRelatedCallees, - ThreadRelatedIncompleteCallSites - )), LazyInterproceduralStringAnalysis // LazyIntraproceduralStringAnalysis ) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index cb8ad9c37c..b10d0d2a74 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -1,7 +1,6 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.fpcf.fixtures.string_analysis; -import com.sun.corba.se.impl.util.PackagePrefixChecker; import org.opalj.fpcf.fixtures.string_analysis.hierarchies.GreetingService; import org.opalj.fpcf.fixtures.string_analysis.hierarchies.HelloGreeting; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; @@ -12,7 +11,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.lang.reflect.Method; -import java.rmi.Remote; import java.util.Scanner; import static org.opalj.fpcf.properties.string_analysis.StringConstancyLevel.*; @@ -431,24 +429,6 @@ public void getPaintShader(boolean getPaintType, int spreadMethod, boolean alpha analyzeString(shaderName); } - @StringDefinitionsCollection( - value = "a case taken from com.sun.corba.se.impl.util.Utility#tieName and slightly " - + "adapted", - stringDefinitions = { - @StringDefinitions( - expectedLevel = PARTIALLY_CONSTANT, - expectedStrings = "(.*.*_tie|.*_tie)" - ) - }) - public String tieName(String var0, Remote remote) { - PackagePrefixChecker ppc = new PackagePrefixChecker(); - String s = PackagePrefixChecker.hasOffendingPrefix(tieNameForCompiler(var0)) ? - PackagePrefixChecker.packagePrefix() + tieNameForCompiler(var0) : - tieNameForCompiler(var0); - analyzeString(s); - return s; - } - /** * Necessary for the tieName test. */ diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index d713ffadba..444b0c00a9 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -17,32 +17,17 @@ import org.opalj.br.cfg.CFG import org.opalj.br.Annotations import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.FPCFAnalysesManagerKey -import org.opalj.br.fpcf.cg.properties.ReflectionRelatedCallees -import org.opalj.br.fpcf.cg.properties.SerializationRelatedCallees -import org.opalj.br.fpcf.cg.properties.StandardInvokeCallees -import org.opalj.br.fpcf.cg.properties.ThreadRelatedIncompleteCallSites import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.PropertyStoreKey -import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis -import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall -import org.opalj.tac.fpcf.analyses.cg.reflection.TriggeredReflectionRelatedCallsAnalysis -import org.opalj.tac.fpcf.analyses.cg.RTACallGraphAnalysisScheduler -import org.opalj.tac.fpcf.analyses.cg.TriggeredFinalizerAnalysisScheduler -import org.opalj.tac.fpcf.analyses.cg.TriggeredSerializationRelatedCallsAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.V -import org.opalj.tac.fpcf.analyses.TriggeredSystemPropertiesAnalysis -import org.opalj.tac.fpcf.analyses.cg.LazyCalleesAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredStaticInitializerAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredThreadRelatedCallsAnalysis -import org.opalj.tac.fpcf.analyses.TACAITransformer -import org.opalj.tac.fpcf.analyses.cg.TriggeredInstantiatedTypesAnalysis -import org.opalj.tac.fpcf.analyses.cg.TriggeredLoadedClassesAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis +import org.opalj.tac.EagerDetachedTACAIKey +import org.opalj.tac.cg.RTACallGraphKey /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -90,7 +75,7 @@ sealed class StringAnalysisTestRunner( // We need a "method to entity" matching for the evaluation (see further below) val m2e = mutable.HashMap[Method, Entity]() - val tacProvider = p.get(DefaultTACAIKey) + val tacProvider = p.get(EagerDetachedTACAIKey) allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) @@ -219,7 +204,7 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { project: Project[URL] ): Iterable[(V, Method)] = { val entitiesToAnalyze = ListBuffer[(V, Method)]() - val tacProvider = project.get(DefaultTACAIKey) + val tacProvider = project.get(EagerDetachedTACAIKey) project.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) @@ -244,25 +229,9 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { ) val p = Project(runner.getRelevantProjectFiles, Array[File]()) + p.get(RTACallGraphKey) val manager = p.get(FPCFAnalysesManagerKey) val analysesToRun = Set( - TACAITransformer, - LazyL0BaseAIAnalysis, - RTACallGraphAnalysisScheduler, - TriggeredStaticInitializerAnalysis, - TriggeredLoadedClassesAnalysis, - TriggeredFinalizerAnalysisScheduler, - TriggeredThreadRelatedCallsAnalysis, - TriggeredSerializationRelatedCallsAnalysis, - TriggeredReflectionRelatedCallsAnalysis, - TriggeredSystemPropertiesAnalysis, - TriggeredInstantiatedTypesAnalysis, - LazyCalleesAnalysis(Set( - StandardInvokeCallees, - SerializationRelatedCallees, - ReflectionRelatedCallees, - ThreadRelatedIncompleteCallSites - )), LazyInterproceduralStringAnalysis ) diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index cc2b55061a..8b592e6ff5 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -728,21 +728,6 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( ) } - /** - * @return Returns the dominator tree of this CFG. - * - * @see [[DominatorTree.apply]] - */ - def dominatorTree: DominatorTree = { - DominatorTree( - 0, - basicBlocks.head.predecessors.nonEmpty, - foreachSuccessor, - foreachPredecessor, - basicBlocks.last.endPC - ) - } - // We use this variable for caching, as the loop information of a CFG are permanent and do not // need to be recomputed (see findNaturalLoops for usage) private var naturalLoops: Option[List[List[Int]]] = None diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala index d6af8c8fae..231bca6819 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/EscapeProperty.scala @@ -11,27 +11,6 @@ import org.opalj.fpcf.ExplicitlyNamedProperty import org.opalj.fpcf.OrderedProperty import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation -import org.opalj.fpcf.PropertyStore -import org.opalj.br.analyses.VirtualFormalParameter -import org.opalj.br.instructions.ACONST_NULL -import org.opalj.br.instructions.ALOAD -import org.opalj.br.instructions.ALOAD_0 -import org.opalj.br.instructions.ALOAD_1 -import org.opalj.br.instructions.ALOAD_2 -import org.opalj.br.instructions.ALOAD_3 -import org.opalj.br.instructions.ARETURN -import org.opalj.br.instructions.ATHROW -import org.opalj.br.instructions.BIPUSH -import org.opalj.br.instructions.DLOAD -import org.opalj.br.instructions.FLOAD -import org.opalj.br.instructions.GETSTATIC -import org.opalj.br.instructions.ILOAD -import org.opalj.br.instructions.Instruction -import org.opalj.br.instructions.LDC -import org.opalj.br.instructions.LDC2_W -import org.opalj.br.instructions.LDC_W -import org.opalj.br.instructions.LLOAD -import org.opalj.br.instructions.SIPUSH sealed trait EscapePropertyMetaInformation extends PropertyMetaInformation { @@ -210,89 +189,8 @@ object EscapeProperty extends EscapePropertyMetaInformation { final lazy val key: PropertyKey[EscapeProperty] = PropertyKey.create( Name, - AtMost(NoEscape), - fastTrack _ + AtMost(NoEscape) ) - - private[this] def escapesViaReturnOrThrow(instruction: Instruction): Option[EscapeProperty] = { - instruction.opcode match { - case ARETURN.opcode ⇒ Some(EscapeViaReturn) - case ATHROW.opcode ⇒ Some(EscapeViaAbnormalReturn) - case _ ⇒ throw new IllegalArgumentException() - } - } - - def fastTrack(ps: PropertyStore, e: Entity): Option[EscapeProperty] = e match { - case fp @ VirtualFormalParameter(dm: DefinedMethod, _) if dm.definedMethod.body.isDefined ⇒ - val parameterIndex = fp.parameterIndex - if (parameterIndex >= 0 && dm.descriptor.parameterType(parameterIndex).isBaseType) - Some(NoEscape) - else { - val m = dm.definedMethod - val code = dm.definedMethod.body.get - code.codeSize match { - case 1 ⇒ - Some(NoEscape) - case 2 ⇒ - if (m.descriptor.returnType.isBaseType) { - Some(NoEscape) - } else { - code.instructions(0).opcode match { - case ACONST_NULL.opcode ⇒ - Some(NoEscape) - - case ALOAD_0.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 0) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else - Some(NoEscape) - - case ALOAD_1.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 1) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else - Some(NoEscape) - case ALOAD_2.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 2) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else - Some(NoEscape) - case ALOAD_3.opcode ⇒ - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, 3) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(1)) - else - Some(NoEscape) - } - } - case 3 ⇒ - code.instructions(0).opcode match { - case BIPUSH.opcode | ILOAD.opcode | FLOAD.opcode | - LLOAD.opcode | DLOAD.opcode | LDC.opcode ⇒ - Some(NoEscape) - case ALOAD.opcode ⇒ - val index = code.instructions(0).asInstanceOf[ALOAD].lvIndex - if (registerIndexToParameterIndex(m.isStatic, m.descriptor, index) == parameterIndex) - escapesViaReturnOrThrow(code.instructions(2)) - else - Some(NoEscape) - case _ ⇒ None - } - case 4 ⇒ - code.instructions(0).opcode match { - case LDC_W.opcode | LDC2_W.opcode | SIPUSH.opcode | GETSTATIC.opcode ⇒ - Some(NoEscape) - case _ ⇒ None - } - - case _ ⇒ - None - } - - } - case _ ⇒ - None - } - } /** diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala index a5fb9b9e57..dbe7f255a8 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/ReturnValueFreshness.scala @@ -7,20 +7,6 @@ package properties import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey import org.opalj.fpcf.PropertyMetaInformation -import org.opalj.fpcf.PropertyStore -import org.opalj.br.instructions.AALOAD -import org.opalj.br.instructions.ACONST_NULL -import org.opalj.br.instructions.ALOAD -import org.opalj.br.instructions.ALOAD_0 -import org.opalj.br.instructions.ALOAD_1 -import org.opalj.br.instructions.ALOAD_2 -import org.opalj.br.instructions.ALOAD_3 -import org.opalj.br.instructions.ARETURN -import org.opalj.br.instructions.ATHROW -import org.opalj.br.instructions.Instruction -import org.opalj.br.instructions.LDC -import org.opalj.br.instructions.LDC_W -import org.opalj.br.instructions.NEWARRAY sealed trait ReturnValueFreshnessPropertyMetaInformation extends PropertyMetaInformation { final type Self = ReturnValueFreshness @@ -57,73 +43,8 @@ object ReturnValueFreshness extends ReturnValueFreshnessPropertyMetaInformation // Name of the property "ReturnValueFreshness", // fallback value - NoFreshReturnValue, - fastTrackPropertyFunction _ + NoFreshReturnValue ) - - def fastTrackPropertyFunction( - ps: PropertyStore, dm: DeclaredMethod - ): Option[ReturnValueFreshness] = { - if (dm.descriptor.returnType.isBaseType) - Some(PrimitiveReturnValue) - else if (!dm.hasSingleDefinedMethod) - Some(NoFreshReturnValue) - else if (dm.declaringClassType.isArrayType && dm.descriptor == MethodDescriptor.JustReturnsObject && dm.name == "clone") - Some(FreshReturnValue) - else { - val m = dm.definedMethod - if (m.body.isEmpty) - Some(NoFreshReturnValue) - else { - val code = m.body.get - code.codeSize match { - case 2 ⇒ code.instructions(0).opcode match { - case ACONST_NULL.opcode ⇒ Some(FreshReturnValue) - case ALOAD_0.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) - case ALOAD_1.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) - case ALOAD_2.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) - case ALOAD_3.opcode ⇒ normalAndAbnormalReturn(code.instructions(1)) - case _ ⇒ None - } - case 3 ⇒ code.instructions(0).opcode match { - case LDC.opcode ⇒ Some(FreshReturnValue) - case ALOAD.opcode ⇒ normalAndAbnormalReturn(code.instructions(2)) - case _ ⇒ None - } - - case 4 ⇒ - val i1 = code.instructions(1) - val i2 = code.instructions(2) - if (i1 != null && i1.opcode == NEWARRAY.opcode) - Some(FreshReturnValue) - else if (code.instructions(0).opcode == LDC_W.opcode) - Some(FreshReturnValue) - else if (i2 != null && i2.opcode == AALOAD.opcode) - normalAndAbnormalReturn(code.instructions(3)) - else - None - - case 1 ⇒ throw new IllegalStateException(s"${m.toJava} unexpected bytecode: ${code.instructions.mkString}") - - case _ ⇒ None - } - } - } - } - - private[this] def normalAndAbnormalReturn( - instr: Instruction - ): Option[ReturnValueFreshness] = instr.opcode match { - case ATHROW.opcode ⇒ - Some(FreshReturnValue) - - case ARETURN.opcode ⇒ - Some(NoFreshReturnValue) - - case _ ⇒ - throw new IllegalArgumentException(s"unexpected instruction $instr") - } - } /** diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala index 4e45114f3b..983a5c5adb 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/StringConstancyProperty.scala @@ -53,7 +53,7 @@ object StringConstancyProperty extends Property with StringConstancyPropertyMeta (_: PropertyStore, _: FallbackReason, _: Entity) ⇒ { // TODO: Using simple heuristics, return a better value for some easy cases lb - }, + } ) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala index 93676015d2..585697b1c6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralComputationState.scala @@ -8,10 +8,10 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.Property import org.opalj.value.ValueInformation -import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.Method +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.Path import org.opalj.tac.DUVar import org.opalj.tac.TACMethodParameter @@ -60,7 +60,7 @@ case class InterproceduralComputationState(entity: P, fieldWriteThreshold: Int = /** * Callers information regarding the declared method that corresponds to the entity's method */ - var callers: CallersProperty = _ + var callers: Callers = _ /** * If not empty, this routine can only produce an intermediate result diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala index af3dd7330e..7cb32bcc74 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/InterproceduralStringAnalysis.scala @@ -21,12 +21,12 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler -import org.opalj.br.fpcf.cg.properties.Callees -import org.opalj.br.fpcf.cg.properties.CallersProperty import org.opalj.br.fpcf.properties.StringConstancyProperty import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.FieldType import org.opalj.br.analyses.FieldAccessInformationKey +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.tac.Stmt import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.AbstractPathFinder @@ -108,7 +108,7 @@ class InterproceduralStringAnalysis( * bounds can be used for the interim result. */ private def getInterimResult( - state: InterproceduralComputationState, + state: InterproceduralComputationState ): InterimResult[StringConstancyProperty] = InterimResult( state.entity, computeNewLowerBound(state), @@ -237,7 +237,7 @@ class InterproceduralStringAnalysis( if (requiresCallersInfo) { val dm = declaredMethods(state.entity._2) - val callersEOptP = ps(dm, CallersProperty.key) + val callersEOptP = ps(dm, Callers.key) if (callersEOptP.hasUBP) { state.callers = callersEOptP.ub if (!registerParams(state)) { @@ -296,7 +296,7 @@ class InterproceduralStringAnalysis( state.computedLeanPath.elements.head match { case FlatPathElement(i) ⇒ state.fpe2sci.contains(i) && state.fpe2sci(i).length == 1 && - state.fpe2sci(i).head == StringConstancyInformation.getNeutralElement + state.fpe2sci(i).head == StringConstancyInformation.getNeutralElement case _ ⇒ false } } else false @@ -357,8 +357,8 @@ class InterproceduralStringAnalysis( state.dependees = eps :: state.dependees getInterimResult(state) } - case CallersProperty.key ⇒ eps match { - case FinalP(callers: CallersProperty) ⇒ + case Callers.key ⇒ eps match { + case FinalP(callers: Callers) ⇒ state.callers = callers if (state.dependees.isEmpty) { registerParams(state) @@ -514,7 +514,7 @@ class InterproceduralStringAnalysis( val callers = state.callers.callers(declaredMethods).toSeq if (callers.length > callersThreshold) { state.params.append( - state.entity._2.parameterTypes.map{ + state.entity._2.parameterTypes.map { _: FieldType ⇒ StringConstancyInformation.lb }.to[ListBuffer] ) @@ -523,7 +523,7 @@ class InterproceduralStringAnalysis( var hasIntermediateResult = false callers.zipWithIndex.foreach { - case ((m, pc), methodIndex) ⇒ + case ((m, pc, _), methodIndex) ⇒ val tac = propertyStore(m.definedMethod, TACAI.key).ub.tac.get val params = tac.stmts(tac.pcToIndex(pc)) match { case Assignment(_, _, fc: FunctionCall[V]) ⇒ fc.params @@ -917,7 +917,7 @@ sealed trait InterproceduralStringAnalysisScheduler extends FPCFAnalysisSchedule * Executor for the lazy analysis. */ object LazyInterproceduralStringAnalysis - extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { + extends InterproceduralStringAnalysisScheduler with FPCFLazyAnalysisScheduler { override def register( p: SomeProject, ps: PropertyStore, analysis: InitializationData diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala index de7933b826..e41ae3473a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/IntraproceduralStringAnalysis.scala @@ -20,8 +20,8 @@ import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.ExprStmt import org.opalj.tac.Stmt @@ -35,8 +35,8 @@ import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.PathTransformer import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.SubPath import org.opalj.tac.fpcf.analyses.string_analysis.preprocessing.WindowPathFinder import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.DefaultTACAIKey import org.opalj.tac.DUVar +import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.TACMethodParameter import org.opalj.tac.TACode @@ -88,7 +88,7 @@ class IntraproceduralStringAnalysis( // sci stores the final StringConstancyInformation (if it can be determined now at all) var sci = StringConstancyProperty.lb.stringConstancyInformation - val tacProvider = p.get(DefaultTACAIKey) + val tacProvider = p.get(EagerDetachedTACAIKey) val tac = tacProvider(data._2) // Uncomment the following code to get the TAC from the property store diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala index 80e40da375..be66ace930 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/AbstractStringInterpreter.scala @@ -13,8 +13,8 @@ import org.opalj.br.cfg.CFG import org.opalj.br.Method import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.DefinedMethod -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.tac.Stmt import org.opalj.tac.TACStmts diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala index e78b63c905..17c4b6a9f5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralFieldInterpreter.scala @@ -34,7 +34,7 @@ class InterproceduralFieldInterpreter( state: InterproceduralComputationState, exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, - fieldAccessInformation: FieldAccessInformation, + fieldAccessInformation: FieldAccessInformation ) extends AbstractStringInterpreter(state.tac.cfg, exprHandler) { override type T = FieldRead[V] @@ -140,9 +140,9 @@ class InterproceduralFieldInterpreter( * [[PutStatic]] or [[PutField]]. */ private def extractUVarFromPut(field: Stmt[V]): V = field match { - case PutStatic(_, _, _, _, value) ⇒ value.asVar + case PutStatic(_, _, _, _, value) ⇒ value.asVar case PutField(_, _, _, _, _, value) ⇒ value.asVar - case _ ⇒ throw new IllegalArgumentException(s"Type of $field is currently not supported!") + case _ ⇒ throw new IllegalArgumentException(s"Type of $field is currently not supported!") } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala index 9f687a6b3d..76814a6b66 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralInterpretationHandler.scala @@ -10,8 +10,8 @@ import org.opalj.fpcf.Result import org.opalj.value.ValueInformation import org.opalj.br.analyses.DeclaredMethods import org.opalj.br.analyses.FieldAccessInformation -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.ai.ImmediateVMExceptionsOriginOffset import org.opalj.tac.fpcf.analyses.string_analysis.V @@ -68,7 +68,7 @@ class InterproceduralInterpretationHandler( ps: PropertyStore, declaredMethods: DeclaredMethods, fieldAccessInformation: FieldAccessInformation, - state: InterproceduralComputationState, + state: InterproceduralComputationState ) extends InterpretationHandler(tac) { /** @@ -422,7 +422,7 @@ object InterproceduralInterpretationHandler { ps: PropertyStore, declaredMethods: DeclaredMethods, fieldAccessInformation: FieldAccessInformation, - state: InterproceduralComputationState, + state: InterproceduralComputationState ): InterproceduralInterpretationHandler = new InterproceduralInterpretationHandler( tac, ps, declaredMethods, fieldAccessInformation, state ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala index a124f5cfcf..7ed25ed155 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualFunctionCallInterpreter.scala @@ -31,7 +31,7 @@ class InterproceduralNonVirtualFunctionCallInterpreter( exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, + declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala index e01379a77f..5746452b1e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralNonVirtualMethodCallInterpreter.scala @@ -26,11 +26,11 @@ import org.opalj.tac.fpcf.analyses.string_analysis.InterproceduralComputationSta * @author Patrick Mell */ class InterproceduralNonVirtualMethodCallInterpreter( - cfg: CFG[Stmt[V], TACStmts[V]], + cfg: CFG[Stmt[V], TACStmts[V]], exprHandler: InterproceduralInterpretationHandler, ps: PropertyStore, state: InterproceduralComputationState, - declaredMethods: DeclaredMethods, + declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = NonVirtualMethodCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala index 4c87ccafd2..56789f61e6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralStaticFunctionCallInterpreter.scala @@ -37,7 +37,7 @@ class InterproceduralStaticFunctionCallInterpreter( ps: PropertyStore, state: InterproceduralComputationState, params: List[Seq[StringConstancyInformation]], - declaredMethods: DeclaredMethods, + declaredMethods: DeclaredMethods ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = StaticFunctionCall[V] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala index 8c7c5b4a2b..29808af2c8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/InterproceduralVirtualMethodCallInterpreter.scala @@ -5,11 +5,11 @@ import org.opalj.fpcf.Entity import org.opalj.fpcf.EOptionP import org.opalj.fpcf.FinalEP import org.opalj.br.cfg.CFG -import org.opalj.br.fpcf.cg.properties.Callees import org.opalj.br.fpcf.properties.string_definition.StringConstancyInformation import org.opalj.br.fpcf.properties.string_definition.StringConstancyLevel import org.opalj.br.fpcf.properties.string_definition.StringConstancyType import org.opalj.br.fpcf.properties.StringConstancyProperty +import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala index 14364a2ff5..bfcc1a238a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/NewArrayPreparer.scala @@ -95,7 +95,7 @@ class NewArrayPreparer( allResults = toAppend :: allResults } state.appendToFpe2Sci(defSite, resultSci) - FinalEP(new Integer(defSite), StringConstancyProperty(resultSci)) + FinalEP(Integer.valueOf(defSite), StringConstancyProperty(resultSci)) } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala index 7366d6415b..75f6778094 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string_analysis/interpretation/interprocedural/VirtualFunctionCallPreparationInterpreter.scala @@ -42,7 +42,7 @@ class VirtualFunctionCallPreparationInterpreter( ps: PropertyStore, state: InterproceduralComputationState, declaredMethods: DeclaredMethods, - params: List[Seq[StringConstancyInformation]], + params: List[Seq[StringConstancyInformation]] ) extends AbstractStringInterpreter(cfg, exprHandler) { override type T = VirtualFunctionCall[V] diff --git a/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala b/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala index 87daff4bfd..658fa7dd17 100644 --- a/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala +++ b/OPAL/tac/src/test/scala/org/opalj/tac/TACAITest.scala @@ -45,66 +45,66 @@ class TACAITest extends FunSpec with Matchers { // FIXME: These two tests fail. Rerun them as soon as #10 is fixed. if (!test.startsWith("inlining \"trivial\" method") && !test.startsWith("chained method call")) - it(test + s" ($className.$methodName using $domainName)") { + it(test + s" ($className.$methodName using $domainName)") { - val cfOption = p.classFile(ObjectType(className.replace('.', '/'))) - if (cfOption.isEmpty) - fail(s"cannot find class: $className") - val cf = cfOption.get + val cfOption = p.classFile(ObjectType(className.replace('.', '/'))) + if (cfOption.isEmpty) + fail(s"cannot find class: $className") + val cf = cfOption.get - val mChain = cf.findMethod(methodName) - if (mChain.size != 1) - fail(s"invalid method name: $className{ $methodName; found: $mChain }") - val m = mChain.head + val mChain = cf.findMethod(methodName) + if (mChain.size != 1) + fail(s"invalid method name: $className{ $methodName; found: $mChain }") + val m = mChain.head - if (m.body.isEmpty) - fail(s"method body is empty: ${m.toJava}") + if (m.body.isEmpty) + fail(s"method body is empty: ${m.toJava}") - val d: Domain with RecordDefUse = - Class. - forName(domainName).asInstanceOf[Class[Domain with RecordDefUse]]. - getConstructor(classOf[Project[_]], classOf[Method]). - newInstance(p, m) - val aiResult = BaseAI(m, d) - val TACode(params, code, _, cfg, _) = TACAI(m, p.classHierarchy, aiResult, false)(Nil) - val actual = ToTxt(params, code, cfg, skipParams = false, indented = false, true) + val d: Domain with RecordDefUse = + Class. + forName(domainName).asInstanceOf[Class[Domain with RecordDefUse]]. + getConstructor(classOf[Project[_]], classOf[Method]). + newInstance(p, m) + val aiResult = BaseAI(m, d) + val TACode(params, code, _, cfg, _) = TACAI(m, p.classHierarchy, aiResult, false)(Nil) + val actual = ToTxt(params, code, cfg, skipParams = false, indented = false, true) - val simpleDomainName = domainName.stripPrefix("org.opalj.ai.domain.") - val expectedFileName = - projectName.substring(0, projectName.indexOf('.')) + - s"-$jdk-$className-$methodName-$simpleDomainName.tac.txt" - val expectedInputStream = this.getClass.getResourceAsStream(expectedFileName) - if (expectedInputStream eq null) - fail( - s"missing expected 3-adddress code representation: $expectedFileName;"+ - s"current representation:\n${actual.mkString("\n")}" - ) - val expected = Source.fromInputStream(expectedInputStream)(UTF8).getLines().toList + val simpleDomainName = domainName.stripPrefix("org.opalj.ai.domain.") + val expectedFileName = + projectName.substring(0, projectName.indexOf('.')) + + s"-$jdk-$className-$methodName-$simpleDomainName.tac.txt" + val expectedInputStream = this.getClass.getResourceAsStream(expectedFileName) + if (expectedInputStream eq null) + fail( + s"missing expected 3-adddress code representation: $expectedFileName;"+ + s"current representation:\n${actual.mkString("\n")}" + ) + val expected = Source.fromInputStream(expectedInputStream)(UTF8).getLines().toList - // check that both files are identical: - val actualIt = actual.iterator - val expectedIt = expected.iterator - while (actualIt.hasNext && expectedIt.hasNext) { - val actualLine = actualIt.next() - val expectedLine = expectedIt.next() - if (actualLine != expectedLine) + // check that both files are identical: + val actualIt = actual.iterator + val expectedIt = expected.iterator + while (actualIt.hasNext && expectedIt.hasNext) { + val actualLine = actualIt.next() + val expectedLine = expectedIt.next() + if (actualLine != expectedLine) + fail( + s"comparison failed:\n$actualLine\n\t\tvs. (expected)\n"+ + s"$expectedLine\ncomputed representation:\n"+ + actual.mkString("\n") + ) + } + if (actualIt.hasNext) + fail( + "actual is longer than expected - first line: "+actualIt.next()+ + "\n computed representation:\n"+actual.mkString("\n") + ) + if (expectedIt.hasNext) fail( - s"comparison failed:\n$actualLine\n\t\tvs. (expected)\n"+ - s"$expectedLine\ncomputed representation:\n"+ - actual.mkString("\n") + "expected is longer than actual - first line: "+expectedIt.next()+ + "\n computed representation:\n"+actual.mkString("\n") ) } - if (actualIt.hasNext) - fail( - "actual is longer than expected - first line: "+actualIt.next()+ - "\n computed representation:\n"+actual.mkString("\n") - ) - if (expectedIt.hasNext) - fail( - "expected is longer than actual - first line: "+expectedIt.next()+ - "\n computed representation:\n"+actual.mkString("\n") - ) - } } } } From 51338dac136ddea77ad1faedd5f690c7e7e45bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20K=C3=BCbler?= Date: Mon, 3 Feb 2020 13:43:10 +0100 Subject: [PATCH 316/316] minor refactoring in tests --- .../InterproceduralTestMethods.java | 5 +- .../org/opalj/fpcf/StringAnalysisTest.scala | 89 ++++++++----------- 2 files changed, 37 insertions(+), 57 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java index b10d0d2a74..47cf5c7d3f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/string_analysis/InterproceduralTestMethods.java @@ -5,7 +5,6 @@ import org.opalj.fpcf.fixtures.string_analysis.hierarchies.HelloGreeting; import org.opalj.fpcf.properties.string_analysis.StringDefinitions; import org.opalj.fpcf.properties.string_analysis.StringDefinitionsCollection; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; import javax.management.remote.rmi.RMIServer; import java.io.File; @@ -604,14 +603,14 @@ public void functionWithNoReturnValueTest1() { * Belongs to functionWithNoReturnValueTest1. */ public String noReturnFunction1() { - throw new NotImplementedException(); + throw new RuntimeException(); } /** * Belongs to functionWithNoReturnValueTest1. */ public static String noReturnFunction2() { - throw new NotImplementedException(); + throw new RuntimeException(); } @StringDefinitionsCollection( diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala index 444b0c00a9..b359494a68 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/StringAnalysisTest.scala @@ -5,11 +5,9 @@ package fpcf import java.io.File import java.net.URL -import scala.collection.mutable import scala.collection.mutable.ListBuffer import org.opalj.collection.immutable.Chain -import org.opalj.collection.immutable.ConstArray import org.opalj.br.analyses.Project import org.opalj.br.Annotation import org.opalj.br.Method @@ -22,12 +20,11 @@ import org.opalj.br.fpcf.PropertyStoreKey import org.opalj.tac.Stmt import org.opalj.tac.TACStmts import org.opalj.tac.VirtualMethodCall -import org.opalj.tac.fpcf.analyses.string_analysis.IntraproceduralStringAnalysis import org.opalj.tac.fpcf.analyses.string_analysis.LazyInterproceduralStringAnalysis -import org.opalj.tac.fpcf.analyses.string_analysis.V import org.opalj.tac.fpcf.analyses.string_analysis.LazyIntraproceduralStringAnalysis import org.opalj.tac.EagerDetachedTACAIKey import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.string_analysis.V /** * @param fqTestMethodsClass The fully-qualified name of the class that contains the test methods. @@ -67,16 +64,12 @@ sealed class StringAnalysisTestRunner( def getStringDefinitionsFromCollection(a: Annotations, index: Int): Annotation = a.head.elementValuePairs(1).value.asArrayValue.values(index).asAnnotationValue.annotation - def determineEAS( - p: Project[URL], - ps: PropertyStore, - allMethodsWithBody: ConstArray[Method] - ): Traversable[((V, Method), String ⇒ String, List[Annotation])] = { - // We need a "method to entity" matching for the evaluation (see further below) - val m2e = mutable.HashMap[Method, Entity]() - - val tacProvider = p.get(EagerDetachedTACAIKey) - allMethodsWithBody.filter { + def determineEntitiesToAnalyze( + project: Project[URL] + ): Iterable[(V, Method)] = { + val entitiesToAnalyze = ListBuffer[(V, Method)]() + val tacProvider = project.get(EagerDetachedTACAIKey) + project.allMethodsWithBody.filter { _.runtimeInvisibleAnnotations.foldLeft(false)( (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) ) @@ -84,18 +77,21 @@ sealed class StringAnalysisTestRunner( StringAnalysisTestRunner.extractUVars( tacProvider(m).cfg, fqTestMethodsClass, nameTestMethod ).foreach { uvar ⇒ - if (!m2e.contains(m)) { - m2e += m → ListBuffer(uvar) - } else { - m2e(m).asInstanceOf[ListBuffer[V]].append(uvar) - } - //ps.force((uvar, m), StringConstancyProperty.key) - } + entitiesToAnalyze.append((uvar, m)) + } } + entitiesToAnalyze + } + def determineEAS( + entities: Iterable[(V, Method)], + project: Project[URL] + ): Traversable[((V, Method), String ⇒ String, List[Annotation])] = { + val m2e = entities.groupBy(_._2).iterator.map(e ⇒ e._1 → e._2.map(k ⇒ k._1)).toMap // As entity, we need not the method but a tuple (DUVar, Method), thus this transformation - val eas = methodsWithAnnotations(p).filter(am ⇒ m2e.contains(am._1)).flatMap { am ⇒ - m2e(am._1).asInstanceOf[ListBuffer[V]].zipWithIndex.map { + + val eas = methodsWithAnnotations(project).filter(am ⇒ m2e.contains(am._1)).flatMap { am ⇒ + m2e(am._1).zipWithIndex.map { case (duvar, index) ⇒ Tuple3( (duvar, am._1), @@ -107,7 +103,6 @@ sealed class StringAnalysisTestRunner( eas } - } object StringAnalysisTestRunner { @@ -165,15 +160,20 @@ class IntraproceduralStringAnalysisTest extends PropertiesTest { val p = Project(runner.getRelevantProjectFiles, Array[File]()) val manager = p.get(FPCFAnalysesManagerKey) - val (ps, _) = manager.runAll(LazyIntraproceduralStringAnalysis) - val testContext = TestContext(p, ps, List(new IntraproceduralStringAnalysis(p))) + val ps = p.get(PropertyStoreKey) + val entities = runner.determineEntitiesToAnalyze(p) + val (_, analyses) = manager.runAll( + List(LazyIntraproceduralStringAnalysis), + { _: Chain[ComputationSpecification[FPCFAnalysis]] ⇒ + entities.foreach(ps.force(_, StringConstancyProperty.key)) + } + ) - LazyIntraproceduralStringAnalysis.init(p, ps) - LazyIntraproceduralStringAnalysis.schedule(ps, null) + val testContext = TestContext(p, ps, analyses.map(_._2)) - val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) + val eas = runner.determineEAS(entities, p) - testContext.propertyStore.shutdown() + ps.shutdown() validateProperties(testContext, eas, Set("StringConstancy")) ps.waitOnPhaseCompletion() } @@ -200,27 +200,6 @@ object IntraproceduralStringAnalysisTest { */ class InterproceduralStringAnalysisTest extends PropertiesTest { - private def determineEntitiesToAnalyze( - project: Project[URL] - ): Iterable[(V, Method)] = { - val entitiesToAnalyze = ListBuffer[(V, Method)]() - val tacProvider = project.get(EagerDetachedTACAIKey) - project.allMethodsWithBody.filter { - _.runtimeInvisibleAnnotations.foldLeft(false)( - (exists, a) ⇒ exists || StringAnalysisTestRunner.isStringUsageAnnotation(a) - ) - } foreach { m ⇒ - StringAnalysisTestRunner.extractUVars( - tacProvider(m).cfg, - InterproceduralStringAnalysisTest.fqTestMethodsClass, - InterproceduralStringAnalysisTest.nameTestMethod - ).foreach { uvar ⇒ - entitiesToAnalyze.append((uvar, m)) - } - } - entitiesToAnalyze - } - describe("the org.opalj.fpcf.InterproceduralStringAnalysis is started") { val runner = new StringAnalysisTestRunner( InterproceduralStringAnalysisTest.fqTestMethodsClass, @@ -229,22 +208,24 @@ class InterproceduralStringAnalysisTest extends PropertiesTest { ) val p = Project(runner.getRelevantProjectFiles, Array[File]()) + val entities = runner.determineEntitiesToAnalyze(p) + p.get(RTACallGraphKey) + val ps = p.get(PropertyStoreKey) val manager = p.get(FPCFAnalysesManagerKey) val analysesToRun = Set( LazyInterproceduralStringAnalysis ) - val ps = p.get(PropertyStoreKey) val (_, analyses) = manager.runAll( analysesToRun, { _: Chain[ComputationSpecification[FPCFAnalysis]] ⇒ - determineEntitiesToAnalyze(p).foreach(ps.force(_, StringConstancyProperty.key)) + entities.foreach(ps.force(_, StringConstancyProperty.key)) } ) val testContext = TestContext(p, ps, analyses.map(_._2)) - val eas = runner.determineEAS(p, ps, p.allMethodsWithBody) + val eas = runner.determineEAS(entities, p) ps.waitOnPhaseCompletion() ps.shutdown()